+
+
+
\ No newline at end of file
diff --git a/app/config/locales/templates/_cta.tpl b/app/config/locales/templates/_cta.tpl
new file mode 100644
index 0000000000..634609a09e
--- /dev/null
+++ b/app/config/locales/templates/_cta.tpl
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/app/config/locales/templates/af.email.auth.confirm.tpl b/app/config/locales/templates/af.email.auth.confirm.tpl
index e0a6c40356..a9dbb2af46 100644
--- a/app/config/locales/templates/af.email.auth.confirm.tpl
+++ b/app/config/locales/templates/af.email.auth.confirm.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Hallo {{name}},
-
-
+
+
Om jou epos adres te verifieer, kliek op die web adres hier:
-
- {{redirect}}
-
-
+
+{{cta}}
+
As jy nie aangevra het om jou epos adres te verifieer nie, kan jy die boodskap ignoreer.
-
-
+
+
Baie dankie,
{{project}} span
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/af.email.auth.invitation.tpl b/app/config/locales/templates/af.email.auth.invitation.tpl
index eed6cf69d1..0c1c72c13a 100644
--- a/app/config/locales/templates/af.email.auth.invitation.tpl
+++ b/app/config/locales/templates/af.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hallo,
-
-
+
+
Hierdie epos is vir jou gestuur omdat {{owner}} jou graag wou nooi om 'n spanlid te word van {{team}} by {{project}}.
-
-
+
+
Om by die {{team}} span aan te sluit:
-
- {{redirect}}
-
-
+
+{{cta}}
+
As jy nie belangstel nie, kan jy die boodskap ignoreer.
-
-
+
اتبع هذا الرابط للتحقق من عنوان بريدك الإلكتروني.
-
- {{redirect}}
-
-
+
+{{cta}}
+
إذا لم تطلب التحقق من هذا العنوان، فيمكنك تجاهل هذه الرسالة.
-
-
+
+
Thanks,
فريق {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ar.email.auth.invitation.tpl b/app/config/locales/templates/ar.email.auth.invitation.tpl
index fba4a731e3..78a1321dab 100644
--- a/app/config/locales/templates/ar.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ar.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
مرحبا،
-
-
+
+
تم إرسال هذه الرسالة إليك لأن {{owner}} أراد دعوتك لتصبح عضوًا في فريق {{team}} في {{project}}.
-
-
+
+
اتبع هذا الرابط للانضمام إلى فريق {{team}}:
-
- {{redirect}}
-
-
+
bitte folge diesem Link um deine E-Mail Adresse zu verifizieren.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Bitte ignoriere diese Nachricht, wenn du das Verifizieren deiner E-Mail Adresse nicht beantragt hast.
-
-
+
+
Vielen Dank,
{{project}} Team
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/de.email.auth.invitation.tpl b/app/config/locales/templates/de.email.auth.invitation.tpl
index c6d9162730..ae55630797 100644
--- a/app/config/locales/templates/de.email.auth.invitation.tpl
+++ b/app/config/locales/templates/de.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hallo,
-
-
+
+
diese E-Mail wurde dir geschickt, weil {{owner}} dich eingeladen hat Teammitglied im Team {{team}} bei {{project}} zu werden.
-
-
+
+
Folge diesem Link um dem Team {{team}} beizutreten:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Wenn du daran nicht interessiert bist, kannst du diese Nachricht ignorieren.
-
-
+
Follow this link to verify your email address.
-
- {{redirect}}
-
-
+
+{{cta}}
+
If you didn’t ask to verify this address, you can ignore this message.
-
-
+
+
Thanks,
{{project}} team
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/en.email.auth.invitation.tpl b/app/config/locales/templates/en.email.auth.invitation.tpl
index fda2bbcce0..70ea922c0c 100644
--- a/app/config/locales/templates/en.email.auth.invitation.tpl
+++ b/app/config/locales/templates/en.email.auth.invitation.tpl
@@ -1,27 +1,14 @@
-
-
-
- Hello,
-
-
+
+ Hello,
+
+
This mail was sent to you because {{owner}} wanted to invite you to become a team member at the {{team}} team over at {{project}}.
-
-
- Follow this link to join the {{team}} team:
-
- {{redirect}}
-
-
- If you are not interested, you can ignore this message.
-
-
+
+{{cta}}
+
+ If you are not interested, you can ignore this message.
+
Thanks,
{{project}} team
-
+
\ No newline at end of file
diff --git a/app/config/locales/templates/en.email.auth.recovery.tpl b/app/config/locales/templates/en.email.auth.recovery.tpl
index 82b365546a..a4a982f46f 100644
--- a/app/config/locales/templates/en.email.auth.recovery.tpl
+++ b/app/config/locales/templates/en.email.auth.recovery.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Hello {{name}},
-
-
+
+
Follow this link to reset your {{project}} password.
-
- {{redirect}}
-
-
- If you didn't ask to reset your password, you can ignore this message.
-
-
+
+{{cta}}
+
+ If you didn’t ask to verify this address, you can ignore this message.
+
+
Thanks,
{{project}} team
-
+
\ No newline at end of file
diff --git a/app/config/locales/templates/es.email.auth.confirm.tpl b/app/config/locales/templates/es.email.auth.confirm.tpl
index a5b9235be8..2c728ddce1 100644
--- a/app/config/locales/templates/es.email.auth.confirm.tpl
+++ b/app/config/locales/templates/es.email.auth.confirm.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Hola {{name}},
-
-
+
+
Sigue este enlace para verificar tu dirección de correo.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Si no has solicitado verificar esta dirección, puedes ignorar este mensaje.
-
-
+
Varmista sähköpostiosoite tästä linkistä.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jos et kysynyt tämän sähköpostiosoitteen varmistamista, voit sivuuttaa tämän viestin.
-
-
+
+
kiitos,
{{project}} tiimi
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/fi.email.auth.invitation.tpl b/app/config/locales/templates/fi.email.auth.invitation.tpl
index 7e955d0216..6680259aa7 100644
--- a/app/config/locales/templates/fi.email.auth.invitation.tpl
+++ b/app/config/locales/templates/fi.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hei,
-
-
+
+
Sait tämän sähköpostin koska {{owner}} halusi kutsua sinut jäseneksi {{team}} tiimiin, täällä {{project}}.
-
-
+
Jos et pyytänyt salasanan nollaamista, voit sivuuttaa tämän viestin-
-
-
+
+
Kiitos,
{{project}} tiimi
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/fo.email.auth.confirm.tpl b/app/config/locales/templates/fo.email.auth.confirm.tpl
index 9af52651b8..5400c2ab01 100644
--- a/app/config/locales/templates/fo.email.auth.confirm.tpl
+++ b/app/config/locales/templates/fo.email.auth.confirm.tpl
@@ -1,24 +1,15 @@
-
-
-
Om tú ikke spurdi om at verifisera hesa teldupostur, kannst tú ignorera hesa boð.
-
-
+
+
Takk,
{{project}} lið
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/fo.email.auth.invitation.tpl b/app/config/locales/templates/fo.email.auth.invitation.tpl
index 586daac0e4..c264c7516f 100644
--- a/app/config/locales/templates/fo.email.auth.invitation.tpl
+++ b/app/config/locales/templates/fo.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Halló,
-
-
+
+
Hesa teldupost var send til tín tí {{owner}} vildi bjóða tær at vera eitt lið limur hjá {{team}} á {{project}}
-
-
+
+
Fylg hettar lenka fyrið at bliva vi {{team}} lið:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Om tú ikke er áhugaður, kannst tú ignorera hesa boð
-
-
+
Ακολούθησε αυτό τον σύνδεσμο για να επιβεβαιώσεις τη διεύθυνση email σου.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Αν δεν ζήτησες να επιβεβαιώσεις αυτή τη διεύθυνση, μπορείς να αγνοήσεις αυτό το μήνυμα.
-
-
+
+
Ευχαριστούμε,
Η ομάδα του {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/gr.email.auth.invitation.tpl b/app/config/locales/templates/gr.email.auth.invitation.tpl
index 101a1c673c..a7c0743ff6 100644
--- a/app/config/locales/templates/gr.email.auth.invitation.tpl
+++ b/app/config/locales/templates/gr.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Γεια,
-
-
+
+
Έλαβες αυτό το email επειδή ο {{owner}} σε προσκάλεσε να γίνεις μέλος της ομάδας {{team}} στο {{project}}.
-
-
+
+
Ακολούθησε αυτό τον σύνδεσμο για να γίνεις μέλος της ομάδας {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Άν δεν ενδιαφέρεσαι, μπορείς να αγνοήσεις αυτό το μήνυμα.
-
-
+
נא ללחוץ על הקישור שלהלן כדי לאמת את החשבון שלך.
-
- {{redirect}}
-
-
+
+{{cta}}
+
אם לא ביקשת לאמת את כתובת הדוא״ל, ניתן להתעלם מההודעה זו.
-
-
+
+
בברכה,
צוות {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/he.email.auth.invitation.tpl b/app/config/locales/templates/he.email.auth.invitation.tpl
index a7514affda..9a9226a5ba 100644
--- a/app/config/locales/templates/he.email.auth.invitation.tpl
+++ b/app/config/locales/templates/he.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
שלום,
-
-
+
+
הודעת דוא״ל זו נשלחה אליך כי {{owner}} ביקש להזמינך להצטרף לצוות {{team}} ב־{{project}}.
-
-
+
+
כדי להצטרף לצוות {{team}}, נא ללחוץ על הקישור:
-
- {{redirect}}
-
-
+
+{{cta}}
+
אם איך לך עניין להצטרף לצוות, ניתן להתעלם מהודעת דוא״ל זו.
-
-
+
Անցեք հղումով, որպեսզի հաստատեք Ձեր էլեկտրոնային հասցեն։
-
- {{redirect}}
-
-
+
+{{cta}}
+
Եթե չեք պահանջել էլեկտրոնային հասցեի հաստատում, պարզապես արհամարհեք այս նամակը։
-
-
+
+
Շնորհակալություն,
{{project}} թիմ
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/hy.email.auth.invitation.tpl b/app/config/locales/templates/hy.email.auth.invitation.tpl
index e471c81b63..2d0f125403 100644
--- a/app/config/locales/templates/hy.email.auth.invitation.tpl
+++ b/app/config/locales/templates/hy.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Ողջույն,
-
-
+
+
Դուք ստացել եք այս նամակը, քանի որ {{owner}}-ը հրավիրում է Ձեզ {{team}} խումբ, {{project}} պրոեկտում։
-
-
+
Ikuti link ini untuk memverifikasi alamat email Anda.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jika Anda tidak meminta untuk memverifikasi alamat ini, Anda dapat mengabaikan pesan ini.
-
-
+
+
Terima kasih,
Tim {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/id.email.auth.invitation.tpl b/app/config/locales/templates/id.email.auth.invitation.tpl
index 7ab49b2002..d81a4a5d83 100644
--- a/app/config/locales/templates/id.email.auth.invitation.tpl
+++ b/app/config/locales/templates/id.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Halo,
-
-
+
+
Email ini dikirimkan kepada Anda karena {{owner}} ingin mengundang Anda untuk menjadi anggota tim {{team}} di {{project}}.
-
-
+
+
Ikuti link ini untuk bergabung dengan tim {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jika Anda tidak tertarik, Anda dapat mengabaikan pesan ini.
-
-
+
Þessi póstur var sendur til þín vegna þess að {{owner}} vildi bjóða þér að gerast liðsmaður í {{team}} teymi í {{project}}.
-
-
+
+
Fylgdu þessum hlekk til að ganga í {{team}} liðið:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Ef þú hefur ekki áhuga geturðu hunsað þessi skilaboð.
-
-
+
+
Takk,
{{project}} Teymi
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/is.email.auth.recovery.tpl b/app/config/locales/templates/is.email.auth.recovery.tpl
index fc3bc57722..edddba1ffb 100644
--- a/app/config/locales/templates/is.email.auth.recovery.tpl
+++ b/app/config/locales/templates/is.email.auth.recovery.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Halló {{name}},
-
-
+
+
Fylgdu þessum tengli til að núllstilla lykilorð {{project}}.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Ef þú baðst ekki um að endurstilla lykilorðið þitt geturðu hunsað þessi skilaboð.
-
-
+
+
Takk,
{{project}} Teymi
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/it.email.auth.confirm.tpl b/app/config/locales/templates/it.email.auth.confirm.tpl
index 93ee5e006f..ec7837ec3d 100644
--- a/app/config/locales/templates/it.email.auth.confirm.tpl
+++ b/app/config/locales/templates/it.email.auth.confirm.tpl
@@ -1,25 +1,16 @@
-
-
-
+
Ciao {{name}},
-
-
+
+
Segui questo link per verificare il tuo indirizzo email.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Se non hai chiesto di verificare questo indirizzo, puoi ignorare questo messaggio.
-
-
+
+
Grazie,
Il team di {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/it.email.auth.invitation.tpl b/app/config/locales/templates/it.email.auth.invitation.tpl
index e6eddb5d1b..f4b8e4f93b 100644
--- a/app/config/locales/templates/it.email.auth.invitation.tpl
+++ b/app/config/locales/templates/it.email.auth.invitation.tpl
@@ -1,28 +1,19 @@
-
-
-
+
Ciao,
-
-
+
+
Questa mail ti è stata inviata perchè {{owner}} vuole invitarti a diventare un membro del team {{team}} del progetto {{project}}.
-
-
+
+
Segui questo link per unirti al team {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Se non sei interessato, puoi ignorare questo messaggio.
-
-
+
+
Grazie,
Il team di {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/it.email.auth.recovery.tpl b/app/config/locales/templates/it.email.auth.recovery.tpl
index 64e865fe76..870b3ab0f5 100644
--- a/app/config/locales/templates/it.email.auth.recovery.tpl
+++ b/app/config/locales/templates/it.email.auth.recovery.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Ciao {{name}},
-
-
+
+
Segui questo link per reimpostare la tua password per {{project}}.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Se non hai chiesto di reimpostare la password, puoi ignorare questo messaggio.
-
-
+
+
Grazie,
Il team di {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ja.email.auth.confirm.tpl b/app/config/locales/templates/ja.email.auth.confirm.tpl
index 6c7a89f355..a06f96cd83 100644
--- a/app/config/locales/templates/ja.email.auth.confirm.tpl
+++ b/app/config/locales/templates/ja.email.auth.confirm.tpl
@@ -1,24 +1,15 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ja.email.auth.invitation.tpl b/app/config/locales/templates/ja.email.auth.invitation.tpl
index ed59caff07..26d5de981b 100644
--- a/app/config/locales/templates/ja.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ja.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
Email iki dikirim menyang sampeyan amarga {{owner}} pengin ngajak sampeyan dadi anggota tim ing {{team}} tim ing {{project}}.
-
-
+
+
Tindakake link iki kanggo gabung ing {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Yen sampeyan ora kasengsem, sampeyan bisa nglirwakake pesen iki.
-
-
+
+
Matur suwun,
tim {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/jv.email.auth.recovery.tpl b/app/config/locales/templates/jv.email.auth.recovery.tpl
index ed6c879aeb..1efd86bc77 100644
--- a/app/config/locales/templates/jv.email.auth.recovery.tpl
+++ b/app/config/locales/templates/jv.email.auth.recovery.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Halo {{name}},
-
-
+
+
Tindakake link iki kanggo ngreset {{project}} sandhi.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Yen sampeyan ora njaluk ngreset sandhi, sampeyan bisa nglalekake pesen iki.
-
-
+
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/km.email.auth.invitation.tpl b/app/config/locales/templates/km.email.auth.invitation.tpl
index 910a14451f..a366ed73e9 100644
--- a/app/config/locales/templates/km.email.auth.invitation.tpl
+++ b/app/config/locales/templates/km.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ko.email.auth.invitation.tpl b/app/config/locales/templates/ko.email.auth.invitation.tpl
index 4fc62b8516..21ed74de68 100644
--- a/app/config/locales/templates/ko.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ko.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
Ikuti pautan ini untuk mengesahkan alamat e-mel anda.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jika anda tidak meminta untuk mengesahkan alamat ini, anda boleh mengabaikan mesej ini.
-
-
+
+
Terima kasih,
Kumpulan {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/my.email.auth.invitation.tpl b/app/config/locales/templates/my.email.auth.invitation.tpl
index 891e1eb4de..66e38bb608 100644
--- a/app/config/locales/templates/my.email.auth.invitation.tpl
+++ b/app/config/locales/templates/my.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hello,
-
-
+
+
E-mel ini dihantar kepada anda kerana {{owner}}ingin mengundang Anda untuk menjadi anggota kumpulan {{team}} di dalam {{project}}.
-
-
+
+
Ikuti pautan ini untuk menyertai kumpulan {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Sekiranya anda tidak berminat,anda boleh mengabaikan mesej ini.
-
-
+
Klink op deze link om uw emailadres te valideren.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Als u niet heeft gevraagd om dit adres te verifiëren, kunt u dit bericht negeren.
-
-
+
+
Bedankt,
{{project}} team
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/nl.email.auth.invitation.tpl b/app/config/locales/templates/nl.email.auth.invitation.tpl
index 252e104aff..75ac6f4510 100644
--- a/app/config/locales/templates/nl.email.auth.invitation.tpl
+++ b/app/config/locales/templates/nl.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hallo,
-
-
+
+
Deze mail is naar je gestuurd omdat {{owner}} wilde dat u een lid zou worden in het {{team}} team voor {{project}}.
-
-
+
+
Volg deze link om het team te joinen {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
ALs u niet geïnteresseerd bent, kunt u dit bericht negeren.
-
-
+
Følg denne lenken for å verifisere din e-postadresse.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Hvis du ikke har spurt om å verifisere din e-post, kan du ignorere denne meldingen.
-
-
+
+
Hilsen,
{{project}}-teamet
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/no.email.auth.invitation.tpl b/app/config/locales/templates/no.email.auth.invitation.tpl
index c56e913d3d..00cd9defe5 100644
--- a/app/config/locales/templates/no.email.auth.invitation.tpl
+++ b/app/config/locales/templates/no.email.auth.invitation.tpl
@@ -1,28 +1,19 @@
-
-
-
+
Hei,
-
-
+
+
Denne mailen ble sendt til deg fordi {{owner}} har invitert deg til å bli medlem av {{team}}-teamet på {{project}}.
-
-
+
+
Follow this link to join the {{team}} team:
Følg denne lenken for å bli med på {{team}}-teamet:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Hvis du ikke er interresert kan du ignorere denne meldingen.
-
-
+
Sundun ang link na ito upang ma-verify ang iyong email address.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Kung hindi mo hiniling na i-verify ang address na ito, maaari mong balewalain ang mensaheng ito.
-
-
+
+
Salamat,
Pangkat ng {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ph.email.auth.invitation.tpl b/app/config/locales/templates/ph.email.auth.invitation.tpl
index f8a8668e50..23916efb0c 100644
--- a/app/config/locales/templates/ph.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ph.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Kamusta,
-
-
+
+
Ipinadala ang email na ito sa iyo dahil nais kang anyayahan ni {{ owner }} upang maging isang kasapi ng pangkat ng {{ team }} sa {{ project }}.
-
-
+
+
Sundan ang link na ito upang sumali sa pangkat ng {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Kung hindi ka interesado, maari mong balewalain ang mensahing ito.
-
-
+
Kliknij ten link, aby zweryfikować swój adres e-mail.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jeśli nie prosiłeś o weryfikację tego adresu, możesz zignorować tę wiadomość.
-
-
+
+
Dziękujemy,
zespół {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/pl.email.auth.invitation.tpl b/app/config/locales/templates/pl.email.auth.invitation.tpl
index d11586de75..387b2e4ae8 100644
--- a/app/config/locales/templates/pl.email.auth.invitation.tpl
+++ b/app/config/locales/templates/pl.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Witaj,
-
-
+
+
Ta wiadomość została do Ciebie wysłana, ponieważ {{owner}} chciałby zaprosić Cię do dołączenia do zespołu {{team}} w {{project}}.
-
-
+
+
Kliknij ten link, aby dołączyć do zespołu {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Jeśli nie jesteś zainteresowany, możesz zignorować tę wiadomość.
-
-
+
Acesse o link abaixo para redefinir sua senha do {{project}}.
-
- {{redirect}}
-
-
- Caso você não solicitou a redefinição de senha, por favor ignore esta mensagem.
-
-
+
+{{cta}}
+
+ Caso não tenha solicitado a redefinição de senha, por favor ignore esta mensagem.
+
Por favor, confirme o seu email através do link abaixo.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Se não solicitou a confirmação de email, por favor ignore esta mensagem.
-
-
+
+
Com os melhores cumprimentos,
Equipa {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/pt-pt.email.auth.invitation.tpl b/app/config/locales/templates/pt-pt.email.auth.invitation.tpl
index 429a30be9a..c0bdfa4136 100644
--- a/app/config/locales/templates/pt-pt.email.auth.invitation.tpl
+++ b/app/config/locales/templates/pt-pt.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Olá,
-
-
+
+
Recebeu este email porque {{owner}} deseja convidá-lo a tornar-se membro da equipa {{team}} no {{project}}.
-
-
+
+
Use este link para se juntar à equipa {{team}}:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Se não estiver interessado, por favor ignore esta mensagem.
-
-
+
+
Com os melhores cumprimentos,
Equipa {{project}}
-
Перейдите по ссылке, чтобы подтвердить свой адрес электронной почты.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Если вы не запрашивали подтверждение этого адреса, проигнорируйте это сообщение.
-
-
+
+
Спасибо,
команда {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ru.email.auth.invitation.tpl b/app/config/locales/templates/ru.email.auth.invitation.tpl
index 41de21d09f..7dc637a3de 100644
--- a/app/config/locales/templates/ru.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ru.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Здравствуйте,
-
-
+
+
Это письмо отправлено вам, потому что {{owner}} приглашает стать членом команды {{team}} в проекте {{project}}.
-
-
+
+
Перейдите по ссылке, чтобы присоединиться к команде {{team}} :
-
- {{redirect}}
-
-
+
+{{cta}}
+
Если вы не заинтересованы, проигнорируйте это сообщение.
-
-
+
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/si.email.auth.invitation.tpl b/app/config/locales/templates/si.email.auth.invitation.tpl
index 1a0842d2e8..2fa979751e 100644
--- a/app/config/locales/templates/si.email.auth.invitation.tpl
+++ b/app/config/locales/templates/si.email.auth.invitation.tpl
@@ -1,31 +1,22 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/si.email.auth.recovery.tpl b/app/config/locales/templates/si.email.auth.recovery.tpl
index bcf32b61ba..4df6902070 100644
--- a/app/config/locales/templates/si.email.auth.recovery.tpl
+++ b/app/config/locales/templates/si.email.auth.recovery.tpl
@@ -1,25 +1,15 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/sl.email.auth.confirm.tpl b/app/config/locales/templates/sl.email.auth.confirm.tpl
index e7f15272da..fb6c4a59d1 100644
--- a/app/config/locales/templates/sl.email.auth.confirm.tpl
+++ b/app/config/locales/templates/sl.email.auth.confirm.tpl
@@ -1,24 +1,15 @@
-
-
-
+
Pozdravljeni {{name}},
-
-
+
+
Sledite tej povezavi za potrditev vašega email naslova.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Če niste zahtevali potrditve tega naslova, lahko to sporočilo prezrete.
-
-
+
+
Hvala,
{{project}} ekipa
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/sl.email.auth.invitation.tpl b/app/config/locales/templates/sl.email.auth.invitation.tpl
index 13467789a3..0dc64594b0 100644
--- a/app/config/locales/templates/sl.email.auth.invitation.tpl
+++ b/app/config/locales/templates/sl.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Pozdravljeni,
-
-
+
+
To sporočilo vam je bilo posredovano, ker vas je {{owner}} povabil/a, da postanete član {{team}} ekipe za {{project}}.
-
-
+
+
Sledite tej povezavi, da se pridružite {{team}} ekipi:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Če vas ne zanima, lahko to sporočilo prezrete.
-
-
+
Vänligen följ länken nedan för att verifiera din epostadress.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Om du inte vill verifiera din epostadress så kan du ignorera detta meddelande.
-
-
+
+
Tack,
{{project}}-teamet
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/sv.email.auth.invitation.tpl b/app/config/locales/templates/sv.email.auth.invitation.tpl
index 604e94227d..12f4ddf858 100644
--- a/app/config/locales/templates/sv.email.auth.invitation.tpl
+++ b/app/config/locales/templates/sv.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Hej,
-
-
+
+
{{owner}} vill bjuda in dig att bli del av {{team}}-teamet inom {{project}}.
-
-
+
+
Följ denna länk för att bli del av {{team}}-teamet:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Om du inte är intresserad så kan du ignorera detta meddelande.
-
-
+
இந்த மின்னஞ்சல் உங்களுக்கு அனுப்பப்பட்டது, ஏனெனில் {{owner}} உங்களை {{team}} குழுவின் {{project}} திட்டத்தில் உறுப்பினராக அழைக்க விரும்பினார்கள்.
-
-
+
+
இந்த இணைப்பைப் பின்தொடர்ந்து {{team}} குழுவில் சேரவும்:
-
- {{redirect}}
-
-
+
+{{cta}}
+
இந்த முகவரியை சரிபார்க்க நீங்கள் கேட்கவில்லை என்றால், இந்த செய்தியை நீங்கள் புறக்கணிக்கலாம்.
-
-
+
Aşağıdaki bağlantıyı takip ederek email hesabınızı doğrulayın.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Bu adresi doğrulamayı istemediyseniz, bu mesajı yok sayabilirsiniz.
-
-
+
+
Teşekkürler,
{{project}} takımı
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/tr.email.auth.invitation.tpl b/app/config/locales/templates/tr.email.auth.invitation.tpl
index 07e8f06db4..c3c35b6bbd 100644
--- a/app/config/locales/templates/tr.email.auth.invitation.tpl
+++ b/app/config/locales/templates/tr.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Merhaba,
-
-
+
+
Bu posta size gönderildi, çünkü {{owner}} sizi {{project}} için {{team}} ekibinde takım üyesi olmaya davet etmek istedi.
-
-
+
+
Aşağıdaki bağlantıyı takip ederek {{team}} takımına takılın:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Eğer ilgilenmiyorsanız, bu mesajı yok sayabilirsiniz.
-
-
+
Перейдіть за цим посиланням, та підтвердіть свою електронну адресу
-
- {{redirect}}
-
-
+
+{{cta}}
+
Якщо ви не запитували підтвердження цієї адреси, проігноруйте це повідомлення.
-
-
+
+
Дякуємо,
команда {{project}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/ua.email.auth.invitation.tpl b/app/config/locales/templates/ua.email.auth.invitation.tpl
index 8daaecbaaf..dc4d3aed02 100644
--- a/app/config/locales/templates/ua.email.auth.invitation.tpl
+++ b/app/config/locales/templates/ua.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Вітаємо,
-
-
+
+
Цей лист був надісланий вам тому що {{owner}} хоче запросити вас стати членом команди {{team}} у {{project}}.
-
-
+
+
Перейдіть за цим посиланням щоб приєднатись до команди {{team}} :
-
- {{redirect}}
-
-
+
+{{cta}}
+
Якщо ви не зацікавлені, проігноруйте це повідомлення.
-
-
+
Hãy vào liên kết này để xác nhận địa chỉ email của bạn.
-
- {{redirect}}
-
-
+
+{{cta}}
+
Xin hãy bỏ qua email này nếu bạn không yêu cầu xác nhận địa chỉ này.
-
-
+
+
Xin cảm ơn,
{{project}} team
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/vi.email.auth.invitation.tpl b/app/config/locales/templates/vi.email.auth.invitation.tpl
index 387c537f55..8d222ea8c7 100644
--- a/app/config/locales/templates/vi.email.auth.invitation.tpl
+++ b/app/config/locales/templates/vi.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
+
Xin chào,
-
-
+
+
Bạn nhận được email này vì {{owner}} muốn mời bạn tham gia {{project}} cùng với {{team}} team.
-
-
+
+
Hãy theo liên kết này để tham gia vào {{team}} team:
-
- {{redirect}}
-
-
+
+{{cta}}
+
Nếu bạn không thích tham gia, hãy bỏ qua lời nhắn này.
-
-
+
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/zh-cn.email.auth.invitation.tpl b/app/config/locales/templates/zh-cn.email.auth.invitation.tpl
index 7ee56a401a..4ef8caba5c 100644
--- a/app/config/locales/templates/zh-cn.email.auth.invitation.tpl
+++ b/app/config/locales/templates/zh-cn.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/zh-tw.email.auth.invitation.tpl b/app/config/locales/templates/zh-tw.email.auth.invitation.tpl
index 08fb921fd7..b5e6cc8225 100644
--- a/app/config/locales/templates/zh-tw.email.auth.invitation.tpl
+++ b/app/config/locales/templates/zh-tw.email.auth.invitation.tpl
@@ -1,27 +1,18 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/config/locales/templates/zh-tw.email.auth.recovery.tpl b/app/config/locales/templates/zh-tw.email.auth.recovery.tpl
index 8a473b5537..1144b36b3c 100644
--- a/app/config/locales/templates/zh-tw.email.auth.recovery.tpl
+++ b/app/config/locales/templates/zh-tw.email.auth.recovery.tpl
@@ -1,25 +1,16 @@
-
-
-
+
{{name}} 你好,
-
-
+
+
请点击下方的链接重新设置{{project}}的密码。
Follow this link to reset your {{project}} password.
-
- {{redirect}}
-
-
+
+{{cta}}
+
如果您未曾申请重设密码,请忽略本邮件。
-
-
+
+
谢谢。
来自 {{project}}
-
+
diff --git a/app/config/platforms.php b/app/config/platforms.php
index f05dcde39b..a0b4955a8e 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -21,7 +21,7 @@ return [
'beta' => false,
'family' => APP_PLATFORM_CLIENT,
'prism' => 'javascript',
- 'source' => realpath(__DIR__ . '/../sdks/client-web'),
+ 'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-js.git',
'gitRepoName' => 'sdk-for-js',
'gitUserName' => 'appwrite',
@@ -35,7 +35,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_CLIENT,
'prism' => 'dart',
- 'source' => realpath(__DIR__ . '/../sdks/client-flutter'),
+ 'source' => \realpath(__DIR__ . '/../sdks/client-flutter'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
'gitRepoName' => 'sdk-for-flutter',
'gitUserName' => 'appwrite',
@@ -110,7 +110,7 @@ return [
'beta' => false,
'family' => APP_PLATFORM_CONSOLE,
'prism' => 'console',
- 'source' => realpath(__DIR__ . '/../sdks/console-web'),
+ 'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => null,
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
@@ -134,7 +134,7 @@ return [
'beta' => false,
'family' => APP_PLATFORM_SERVER,
'prism' => 'javascript',
- 'source' => realpath(__DIR__ . '/../sdks/server-nodejs'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-nodejs'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
'gitRepoName' => 'sdk-for-node',
'gitUserName' => 'appwrite',
@@ -148,7 +148,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'typescript',
- 'source' => realpath(__DIR__ . '/../sdks/server-deno'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-deno'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-deno.git',
'gitRepoName' => 'sdk-for-deno',
'gitUserName' => 'appwrite',
@@ -162,7 +162,7 @@ return [
'beta' => false,
'family' => APP_PLATFORM_SERVER,
'prism' => 'php',
- 'source' => realpath(__DIR__ . '/../sdks/server-php'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-php'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
'gitRepoName' => 'sdk-for-php',
'gitUserName' => 'appwrite',
@@ -176,7 +176,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'python',
- 'source' => realpath(__DIR__ . '/../sdks/server-python'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-python'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
'gitRepoName' => 'sdk-for-python',
'gitUserName' => 'appwrite',
@@ -190,7 +190,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'ruby',
- 'source' => realpath(__DIR__ . '/../sdks/server-ruby'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-ruby'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
'gitRepoName' => 'sdk-for-ruby',
'gitUserName' => 'appwrite',
@@ -204,7 +204,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'go',
- 'source' => realpath(__DIR__ . '/../sdks/server-go'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-go'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
'gitRepoName' => 'sdk-for-go',
'gitUserName' => 'appwrite',
@@ -218,7 +218,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'java',
- 'source' => realpath(__DIR__ . '/../sdks/server-java'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-java'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
'gitRepoName' => 'sdk-for-java',
'gitUserName' => 'appwrite',
@@ -232,7 +232,7 @@ return [
'beta' => true,
'family' => APP_PLATFORM_SERVER,
'prism' => 'java',
- 'source' => realpath(__DIR__ . '/../sdks/server-dart'),
+ 'source' => \realpath(__DIR__ . '/../sdks/server-dart'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
'gitRepoName' => 'sdk-for-dart',
'gitUserName' => 'appwrite',
diff --git a/app/config/providers.php b/app/config/providers.php
index 7eb5113d96..505c2898c4 100644
--- a/app/config/providers.php
+++ b/app/config/providers.php
@@ -1,6 +1,22 @@
[
+ 'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis',
+ 'icon' => 'icon-amazon',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false,
+ ],
+ 'apple' => [
+ 'developers' => 'https://developer.apple.com/',
+ 'icon' => 'icon-apple',
+ 'enabled' => true,
+ 'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded.
+ 'beta' => true,
+ 'mock' => false,
+ ],
'bitbucket' => [
'developers' => 'https://developer.atlassian.com/bitbucket',
'icon' => 'icon-bitbucket',
@@ -9,6 +25,30 @@ return [
'beta' => false,
'mock' => false,
],
+ 'bitly' => [
+ 'developers' => 'https://dev.bitly.com/v4_documentation.html',
+ 'icon' => 'icon-bitly',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false
+ ],
+ 'discord' => [
+ 'developers' => 'https://discordapp.com/developers/docs/topics/oauth2',
+ 'icon' => 'icon-discord',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false,
+ ],
+ 'dropbox' => [
+ 'developers' => 'https://www.dropbox.com/developers/documentation',
+ 'icon' => 'icon-dropbox',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false,
+ ],
'facebook' => [
'developers' => 'https://developers.facebook.com/',
'icon' => 'icon-facebook',
@@ -34,20 +74,21 @@ return [
'mock' => false,
],
'google' => [
- 'developers' => 'https://developers.google.com/',
+ 'developers' => 'https://support.google.com/googleapi/answer/6158849',
'icon' => 'icon-google',
'enabled' => true,
'form' => false,
'beta' => false,
'mock' => false,
],
- // 'instagram' => [
- // 'developers' => 'https://www.instagram.com/developer/',
- // 'icon' => 'icon-instagram',
- // 'enabled' => false,
- // 'beta' => false,
- // 'mock' => false,
- // ],
+ 'linkedin' => [
+ 'developers' => 'https://developer.linkedin.com/',
+ 'icon' => 'icon-linkedin',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false,
+ ],
'microsoft' => [
'developers' => 'https://developer.microsoft.com/en-us/',
'icon' => 'icon-windows',
@@ -56,16 +97,17 @@ return [
'beta' => false,
'mock' => false,
],
- // 'twitter' => [
- // 'developers' => 'https://developer.twitter.com/',
- // 'icon' => 'icon-twitter',
- // 'enabled' => false,
- // 'beta' => false,
- // 'mock' => false,
- // ],
- 'linkedin' => [
- 'developers' => 'https://developer.linkedin.com/',
- 'icon' => 'icon-linkedin',
+ 'paypal' => [
+ 'developers' => 'https://developer.paypal.com/docs/api/overview/',
+ 'icon' => 'icon-paypal',
+ 'enabled' => true,
+ 'form' => false,
+ 'beta' => false,
+ 'mock' => false
+ ],
+ 'salesforce' => [
+ 'developers' => 'https://developer.salesforce.com/docs/',
+ 'icon' => 'icon-salesforce',
'enabled' => true,
'form' => false,
'beta' => false,
@@ -79,49 +121,9 @@ return [
'beta' => false,
'mock' => false,
],
- 'dropbox' => [
- 'developers' => 'https://www.dropbox.com/developers/documentation',
- 'icon' => 'icon-dropbox',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false,
- ],
- 'salesforce' => [
- 'developers' => 'https://developer.salesforce.com/docs/',
- 'icon' => 'icon-salesforce',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false,
- ],
- 'apple' => [
- 'developers' => 'https://developer.apple.com/',
- 'icon' => 'icon-apple',
- 'enabled' => true,
- 'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded.
- 'beta' => true,
- 'mock' => false,
- ],
- 'amazon' => [
- 'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis',
- 'icon' => 'icon-amazon',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false,
- ],
- 'vk' => [
- 'developers' => 'https://vk.com/dev',
- 'icon' => 'icon-vk',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false,
- ],
- 'discord' => [
- 'developers' => 'https://discordapp.com/developers/docs/topics/oauth2',
- 'icon' => 'icon-discord',
+ 'spotify' => [
+ 'developers' => 'https://developer.spotify.com/documentation/general/guides/authorization-guide/',
+ 'icon' => 'icon-spotify',
'enabled' => true,
'form' => false,
'beta' => false,
@@ -135,9 +137,9 @@ return [
'beta' => false,
'mock' => false,
],
- 'spotify' => [
- 'developers' => 'https://developer.spotify.com/documentation/general/guides/authorization-guide/',
- 'icon' => 'icon-spotify',
+ 'vk' => [
+ 'developers' => 'https://vk.com/dev',
+ 'icon' => 'icon-vk',
'enabled' => true,
'form' => false,
'beta' => false,
@@ -159,30 +161,20 @@ return [
'beta' => false,
'mock' => false,
],
- 'twitter' => [
- 'developers' => 'https://developer.twitter.com/',
- 'icon' => 'icon-twitter',
- 'enabled' => false,
- 'form' => false,
- 'beta' => false,
- 'mock' => false
- ],
- 'paypal' => [
- 'developers' => 'https://developer.paypal.com/docs/api/overview/',
- 'icon' => 'icon-paypal',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false
- ],
- 'bitly' => [
- 'developers' => 'https://dev.bitly.com/v4_documentation.html',
- 'icon' => 'icon-bitly',
- 'enabled' => true,
- 'form' => false,
- 'beta' => false,
- 'mock' => false
- ],
+ // 'instagram' => [
+ // 'developers' => 'https://www.instagram.com/developer/',
+ // 'icon' => 'icon-instagram',
+ // 'enabled' => false,
+ // 'beta' => false,
+ // 'mock' => false,
+ // ],
+ // 'twitter' => [
+ // 'developers' => 'https://developer.twitter.com/',
+ // 'icon' => 'icon-twitter',
+ // 'enabled' => false,
+ // 'beta' => false,
+ // 'mock' => false,
+ // ],
// Keep Last
'mock' => [
'developers' => 'https://appwrite.io',
diff --git a/app/config/roles.php b/app/config/roles.php
index f462305d97..4d2420ec58 100644
--- a/app/config/roles.php
+++ b/app/config/roles.php
@@ -24,7 +24,6 @@ $logged = [
'projects.write',
'locale.read',
'avatars.read',
- 'health.read',
];
$admins = [
@@ -62,24 +61,23 @@ return [
'files.read',
'locale.read',
'avatars.read',
- 'health.read',
],
],
ROLE_MEMBER => [
'label' => 'Member',
- 'scopes' => array_merge($logged, []),
+ 'scopes' => \array_merge($logged, []),
],
ROLE_ADMIN => [
'label' => 'Admin',
- 'scopes' => array_merge($admins, []),
+ 'scopes' => \array_merge($admins, []),
],
ROLE_DEVELOPER => [
'label' => 'Developer',
- 'scopes' => array_merge($admins, []),
+ 'scopes' => \array_merge($admins, []),
],
ROLE_OWNER => [
'label' => 'Owner',
- 'scopes' => array_merge($logged, $admins, []),
+ 'scopes' => \array_merge($logged, $admins, []),
],
ROLE_APP => [
'label' => 'Application',
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index dd78f84dab..c2c2c642ac 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -1,7 +1,7 @@
init(function() use (&$oauth2Keys) {
continue;
}
- $oauth2Keys[] = 'oauth2'.ucfirst($key);
- $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken';
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
});
@@ -59,21 +60,21 @@ $utopia->post('/v1/account')
->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
->param('name', '', function () { return new Text(100); }, 'User name.', true)
->action(
- function ($email, $password, $name) use ($register, $request, $response, $audit, $projectDB, $project, $webhook, $oauth2Keys) {
+ function ($email, $password, $name) use ($request, $response, $audit, $projectDB, $project, $webhook, $oauth2Keys) {
if ('console' === $project->getId()) {
$whitlistEmails = $project->getAttribute('authWhitelistEmails');
$whitlistIPs = $project->getAttribute('authWhitelistIPs');
$whitlistDomains = $project->getAttribute('authWhitelistDomains');
- if (!empty($whitlistEmails) && !in_array($email, $whitlistEmails)) {
+ if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) {
throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401);
}
- if (!empty($whitlistIPs) && !in_array($request->getIP(), $whitlistIPs)) {
+ if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) {
throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401);
}
- if (!empty($whitlistDomains) && !in_array(substr(strrchr($email, '@'), 1), $whitlistDomains)) {
+ if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) {
throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401);
}
}
@@ -104,8 +105,8 @@ $utopia->post('/v1/account')
'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => Auth::passwordHash($password),
- 'password-update' => time(),
- 'registration' => time(),
+ 'password-update' => \time(),
+ 'registration' => \time(),
'reset' => false,
'name' => $name,
], ['email' => $email]);
@@ -170,7 +171,7 @@ $utopia->post('/v1/account/sessions')
throw new Exception('Invalid credentials', 401); // Wrong password or username
}
- $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$secret = Auth::tokenGenerator();
$session = new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
@@ -213,7 +214,7 @@ $utopia->post('/v1/account/sessions')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
;
}
@@ -240,34 +241,35 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
->label('sdk.methodType', 'webAuth')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
- ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . implode(', ', array_keys(array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
+ ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
->param('success', $oauthDefaultSuccess, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true)
->param('failure', $oauthDefaultFailure, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true)
+ ->param('scopes', [], function () { return new ArrayList(new Text(128)); }, 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.', true)
->action(
- function ($provider, $success, $failure) use ($response, $request, $project) {
+ function ($provider, $success, $failure, $scopes) use ($response, $request, $project) {
$protocol = Config::getParam('protocol');
$callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
- $appId = $project->getAttribute('usersOauth2'.ucfirst($provider).'Appid', '');
- $appSecret = $project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}');
+ $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
+ $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
- $appSecret = json_decode($appSecret, true);
+ $appSecret = \json_decode($appSecret, true);
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
- $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag']));
+ $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
if (empty($appId) || empty($appSecret)) {
throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your '.APP_NAME.' console to continue.', 412);
}
- $classname = 'Appwrite\\Auth\\OAuth2\\'.ucfirst($provider);
+ $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
- if (!class_exists($classname)) {
+ if (!\class_exists($classname)) {
throw new Exception('Provider is not supported', 501);
}
- $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure]);
+ $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes);
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
@@ -282,7 +284,7 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->label('scope', 'public')
->label('docs', false)
->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
+ ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
->param('state', '', function () { return new Text(2048); }, 'Login state params.', true)
->action(
@@ -294,7 +296,7 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
- .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
+ .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
}
);
@@ -305,7 +307,7 @@ $utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->label('origin', '*')
->label('docs', false)
->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
+ ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
->param('state', '', function () { return new Text(2048); }, 'Login state params.', true)
->action(
@@ -317,7 +319,7 @@ $utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
- .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
+ .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
}
);
@@ -329,7 +331,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->label('docs', false)
- ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
+ ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true)
->action(
@@ -339,19 +341,19 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$validateURL = new URL();
- $appId = $project->getAttribute('usersOauth2'.ucfirst($provider).'Appid', '');
- $appSecret = $project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}');
+ $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
+ $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
- $appSecret = json_decode($appSecret, true);
+ $appSecret = \json_decode($appSecret, true);
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
- $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag']));
+ $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
- $classname = 'Appwrite\\Auth\\OAuth2\\'.ucfirst($provider);
+ $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
- if (!class_exists($classname)) {
+ if (!\class_exists($classname)) {
throw new Exception('Provider is not supported', 501);
}
@@ -359,7 +361,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
if (!empty($state)) {
try {
- $state = array_merge($defaultState, $oauth2->parseState($state));
+ $state = \array_merge($defaultState, $oauth2->parseState($state));
} catch (\Exception $exception) {
throw new Exception('Failed to parse login state params as passed from OAuth2 provider');
}
@@ -407,7 +409,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
'first' => true,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'oauth2'.ucfirst($provider).'='.$oauth2ID,
+ 'oauth2'.\ucfirst($provider).'='.$oauth2ID,
],
]) : $user;
@@ -435,8 +437,8 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
'emailVerification' => true,
'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider
'password' => Auth::passwordHash(Auth::passwordGenerator()),
- 'password-update' => time(),
- 'registration' => time(),
+ 'password-update' => \time(),
+ 'registration' => \time(),
'reset' => false,
'name' => $name,
], ['email' => $email]);
@@ -455,7 +457,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
// Create session token, verify user account and update OAuth2 ID and Access Token
$secret = Auth::tokenGenerator();
- $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]],
@@ -467,8 +469,8 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
]);
$user
- ->setAttribute('oauth2'.ucfirst($provider), $oauth2ID)
- ->setAttribute('oauth2'.ucfirst($provider).'AccessToken', $accessToken)
+ ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID)
+ ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken)
->setAttribute('status', Auth::USER_STATUS_ACTIVATED)
->setAttribute('tokens', $session, Document::SET_TYPE_APPEND)
;
@@ -490,7 +492,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
@@ -541,7 +543,7 @@ $utopia->get('/v1/account/prefs')
$prefs = $user->getAttribute('prefs', '{}');
try {
- $prefs = json_decode($prefs, true);
+ $prefs = \json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
@@ -595,7 +597,7 @@ $utopia->get('/v1/account/sessions')
try {
$record = $reader->country($token->getAttribute('ip', ''));
- $sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
+ $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--';
@@ -657,7 +659,7 @@ $utopia->get('/v1/account/logs')
$output[$i] = [
'event' => $log['event'],
'ip' => $log['ip'],
- 'time' => strtotime($log['time']),
+ 'time' => \strtotime($log['time']),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
@@ -668,7 +670,7 @@ $utopia->get('/v1/account/logs')
try {
$record = $reader->country($log['ip']);
- $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
+ $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
@@ -692,7 +694,7 @@ $utopia->patch('/v1/account/name')
->param('name', '', function () { return new Text(100); }, 'User name.')
->action(
function ($name) use ($response, $user, $projectDB, $audit, $oauth2Keys) {
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'name' => $name,
]));
@@ -706,7 +708,7 @@ $utopia->patch('/v1/account/name')
->setParam('resource', 'users/'.$user->getId())
;
- $response->json(array_merge($user->getArrayCopy(array_merge(
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
[
'$id',
'email',
@@ -734,7 +736,7 @@ $utopia->patch('/v1/account/password')
throw new Exception('Invalid credentials', 401);
}
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'password' => Auth::passwordHash($password),
]));
@@ -748,7 +750,7 @@ $utopia->patch('/v1/account/password')
->setParam('resource', 'users/'.$user->getId())
;
- $response->json(array_merge($user->getArrayCopy(array_merge(
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
[
'$id',
'email',
@@ -791,7 +793,7 @@ $utopia->patch('/v1/account/email')
// TODO after this user needs to confirm mail again
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'email' => $email,
'emailVerification' => false,
]));
@@ -806,7 +808,7 @@ $utopia->patch('/v1/account/email')
->setParam('resource', 'users/'.$user->getId())
;
- $response->json(array_merge($user->getArrayCopy(array_merge(
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
[
'$id',
'email',
@@ -829,11 +831,11 @@ $utopia->patch('/v1/account/prefs')
->label('sdk.description', '/docs/references/account/update-prefs.md')
->action(
function ($prefs) use ($response, $user, $projectDB, $audit) {
- $old = json_decode($user->getAttribute('prefs', '{}'), true);
+ $old = \json_decode($user->getAttribute('prefs', '{}'), true);
$old = ($old) ? $old : [];
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
- 'prefs' => json_encode(array_merge($old, $prefs)),
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'prefs' => \json_encode(\array_merge($old, $prefs)),
]));
if (false === $user) {
@@ -848,7 +850,7 @@ $utopia->patch('/v1/account/prefs')
$prefs = $user->getAttribute('prefs', '{}');
try {
- $prefs = json_decode($prefs, true);
+ $prefs = \json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
@@ -869,7 +871,7 @@ $utopia->delete('/v1/account')
->action(
function () use ($response, $user, $projectDB, $audit, $webhook) {
$protocol = Config::getParam('protocol');
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'status' => Auth::USER_STATUS_BLOCKED,
]));
@@ -901,13 +903,13 @@ $utopia->delete('/v1/account')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([]))
;
}
$response
- ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
->noContent()
;
}
@@ -953,14 +955,14 @@ $utopia->delete('/v1/account/sessions/:sessionId')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([]))
;
}
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$response
- ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
;
}
@@ -1006,14 +1008,14 @@ $utopia->delete('/v1/account/sessions')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([]))
;
}
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$response
- ->addCookie(Auth::$cookieName.'_legacy', '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
;
}
}
@@ -1034,7 +1036,7 @@ $utopia->post('/v1/account/recovery')
->param('email', '', function () { return new Email(); }, 'User email.')
->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.')
->action(
- function ($email, $url) use ($request, $response, $projectDB, $register, $audit, $project) {
+ function ($email, $url) use ($request, $response, $projectDB, $mail, $audit, $project) {
$profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1,
'first' => true,
@@ -1054,7 +1056,7 @@ $utopia->post('/v1/account/recovery')
'$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
'type' => Auth::TOKEN_TYPE_RECOVERY,
'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
- 'expire' => time() + Auth::TOKEN_EXPIRATION_RECOVERY,
+ 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(),
]);
@@ -1079,27 +1081,34 @@ $utopia->post('/v1/account/recovery')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]);
$url = Template::unParseURL($url);
- $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.recovery.body'));
+ $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
+ $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.recovery.body'));
+ $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
+
$body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', Locale::getText('account.emails.recovery.title'))
->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
->setParam('{{name}}', $profile->getAttribute('name'))
->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
;
- $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
-
- $mail->addAddress($profile->getAttribute('email', ''), $profile->getAttribute('name', ''));
-
- $mail->Subject = Locale::getText('account.emails.recovery.title');
- $mail->Body = $body->render();
- $mail->AltBody = strip_tags($body->render());
-
- try {
- $mail->send();
- } catch (\Exception $error) {
- throw new Exception('Error sending mail: ' . $error->getMessage(), 500);
- }
+ $mail
+ ->setParam('event', 'account.recovery.create')
+ ->setParam('recipient', $profile->getAttribute('email', ''))
+ ->setParam('name', $profile->getAttribute('name', ''))
+ ->setParam('subject', Locale::getText('account.emails.recovery.title'))
+ ->setParam('body', $body->render())
+ ->trigger();
+ ;
$audit
->setParam('userId', $profile->getId())
@@ -1154,9 +1163,9 @@ $utopia->put('/v1/account/recovery')
Authorization::setRole('user:'.$profile->getId());
- $profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [
+ $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
'password' => Auth::passwordHash($password),
- 'password-update' => time(),
+ 'password-update' => \time(),
'emailVerification' => true,
]));
@@ -1195,7 +1204,7 @@ $utopia->post('/v1/account/verification')
->label('abuse-key', 'url:{url},email:{param-email}')
->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.') // TODO add built-in confirm page
->action(
- function ($url) use ($request, $response, $register, $user, $project, $projectDB, $audit) {
+ function ($url) use ($request, $response, $mail, $user, $project, $projectDB, $audit) {
$verificationSecret = Auth::tokenGenerator();
$verification = new Document([
@@ -1203,7 +1212,7 @@ $utopia->post('/v1/account/verification')
'$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
'type' => Auth::TOKEN_TYPE_VERIFICATION,
'secret' => Auth::hash($verificationSecret), // On way hash encryption to protect DB leak
- 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM,
+ 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(),
]);
@@ -1228,27 +1237,34 @@ $utopia->post('/v1/account/verification')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]);
$url = Template::unParseURL($url);
- $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body'));
+ $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
+ $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body'));
+ $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
+
$body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', Locale::getText('account.emails.verification.title'))
->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
->setParam('{{name}}', $user->getAttribute('name'))
->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
;
- $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
-
- $mail->addAddress($user->getAttribute('email'), $user->getAttribute('name'));
-
- $mail->Subject = Locale::getText('account.emails.verification.title');
- $mail->Body = $body->render();
- $mail->AltBody = strip_tags($body->render());
-
- try {
- $mail->send();
- } catch (\Exception $error) {
- throw new Exception('Problem sending mail: ' . $error->getMessage(), 500);
- }
+ $mail
+ ->setParam('event', 'account.verification.create')
+ ->setParam('recipient', $user->getAttribute('email'))
+ ->setParam('name', $user->getAttribute('name'))
+ ->setParam('subject', Locale::getText('account.emails.verification.title'))
+ ->setParam('body', $body->render())
+ ->trigger()
+ ;
$audit
->setParam('userId', $user->getId())
@@ -1297,7 +1313,7 @@ $utopia->put('/v1/account/verification')
Authorization::setRole('user:'.$profile->getId());
- $profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [
+ $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
'emailVerification' => true,
]));
diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php
index bc493f8cc0..4bff678c5d 100644
--- a/app/controllers/api/avatars.php
+++ b/app/controllers/api/avatars.php
@@ -16,6 +16,7 @@ use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use Utopia\Config\Config;
+use Utopia\Validator\HexColor;
include_once __DIR__ . '/../shared/api.php';
@@ -26,28 +27,28 @@ $types = [
];
$avatarCallback = function ($type, $code, $width, $height, $quality) use ($types, $response, $request) {
- $code = strtolower($code);
- $type = strtolower($type);
+ $code = \strtolower($code);
+ $type = \strtolower($type);
- if (!array_key_exists($type, $types)) {
+ if (!\array_key_exists($type, $types)) {
throw new Exception('Avatar set not found', 404);
}
- if (!array_key_exists($code, $types[$type])) {
+ if (!\array_key_exists($code, $types[$type])) {
throw new Exception('Avatar not found', 404);
}
- if (!extension_loaded('imagick')) {
+ if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500);
}
$output = 'png';
- $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
$path = $types[$type][$code];
$type = 'png';
- if (!is_readable($path)) {
+ if (!\is_readable($path)) {
throw new Exception('File not readable in '.$path, 500);
}
@@ -65,7 +66,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types
;
}
- $resize = new Resize(file_get_contents($path));
+ $resize = new Resize(\file_get_contents($path));
$resize->crop((int) $width, (int) $height);
@@ -91,7 +92,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types
$utopia->get('/v1/avatars/credit-cards/:code')
->desc('Get Credit Card Icon')
- ->param('code', '', function () use ($types) { return new WhiteList(array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: '.implode(', ', array_keys($types['credit-cards'])).'.')
+ ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: '.\implode(', ', \array_keys($types['credit-cards'])).'.')
->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
@@ -106,7 +107,7 @@ $utopia->get('/v1/avatars/credit-cards/:code')
$utopia->get('/v1/avatars/browsers/:code')
->desc('Get Browser Icon')
- ->param('code', '', function () use ($types) { return new WhiteList(array_keys($types['browsers'])); }, 'Browser Code.')
+ ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['browsers'])); }, 'Browser Code.')
->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
@@ -121,7 +122,7 @@ $utopia->get('/v1/avatars/browsers/:code')
$utopia->get('/v1/avatars/flags/:code')
->desc('Get Country Flag')
- ->param('code', '', function () use ($types) { return new WhiteList(array_keys($types['flags'])); }, 'Country Code. ISO Alpha-2 country code format.')
+ ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['flags'])); }, 'Country Code. ISO Alpha-2 country code format.')
->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
@@ -149,8 +150,8 @@ $utopia->get('/v1/avatars/image')
function ($url, $width, $height) use ($response) {
$quality = 80;
$output = 'png';
- $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
@@ -164,11 +165,11 @@ $utopia->get('/v1/avatars/image')
;
}
- if (!extension_loaded('imagick')) {
+ if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500);
}
- $fetch = @file_get_contents($url, false);
+ $fetch = @\file_get_contents($url, false);
if (!$fetch) {
throw new Exception('Image not found', 404);
@@ -218,8 +219,8 @@ $utopia->get('/v1/avatars/favicon')
$height = 56;
$quality = 80;
$output = 'png';
- $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = md5('/v2/avatars/favicon-'.$url);
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5('/v2/avatars/favicon-'.$url);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
@@ -233,26 +234,26 @@ $utopia->get('/v1/avatars/favicon')
;
}
- if (!extension_loaded('imagick')) {
+ if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500);
}
- $curl = curl_init();
+ $curl = \curl_init();
- curl_setopt_array($curl, [
+ \curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_URL => $url,
- CURLOPT_USERAGENT => sprintf(APP_USERAGENT,
+ CURLOPT_USERAGENT => \sprintf(APP_USERAGENT,
Config::getParam('version'),
$request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
),
]);
- $html = curl_exec($curl);
+ $html = \curl_exec($curl);
- curl_close($curl);
+ \curl_close($curl);
if (!$html) {
throw new Exception('Failed to fetch remote URL', 404);
@@ -271,20 +272,20 @@ $utopia->get('/v1/avatars/favicon')
$href = $link->getAttribute('href');
$rel = $link->getAttribute('rel');
$sizes = $link->getAttribute('sizes');
- $absolute = URLParse::unparse(array_merge(parse_url($url), parse_url($href)));
+ $absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href)));
- switch (strtolower($rel)) {
+ switch (\strtolower($rel)) {
case 'icon':
case 'shortcut icon':
//case 'apple-touch-icon':
- $ext = pathinfo(parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
+ $ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
switch ($ext) {
case 'ico':
case 'png':
case 'jpg':
case 'jpeg':
- $size = explode('x', strtolower($sizes));
+ $size = \explode('x', \strtolower($sizes));
$sizeWidth = (isset($size[0])) ? (int) $size[0] : 0;
$sizeHeight = (isset($size[1])) ? (int) $size[1] : 0;
@@ -303,16 +304,16 @@ $utopia->get('/v1/avatars/favicon')
}
if (empty($outputHref) || empty($outputExt)) {
- $default = parse_url($url);
+ $default = \parse_url($url);
$outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
$outputExt = 'ico';
}
if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
- $data = @file_get_contents($outputHref, false);
+ $data = @\file_get_contents($outputHref, false);
- if (empty($data) || (mb_substr($data, 0, 5) === 'get('/v1/avatars/favicon')
;
}
- $fetch = @file_get_contents($outputHref, false);
+ $fetch = @\file_get_contents($outputHref, false);
if (!$fetch) {
throw new Exception('Icon not found', 404);
@@ -383,9 +384,85 @@ $utopia->get('/v1/avatars/qr')
}
$response
- ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->setContentType('image/png')
- ->send('', $writer->writeString($text))
+ ->send($writer->writeString($text))
+ ;
+ }
+ );
+
+$utopia->get('/v1/avatars/initials')
+ ->desc('Get User Initials')
+ ->param('name', '', function () { return new Text(512); }, 'Full Name. When empty, current user name or email will be used.', true)
+ ->param('width', 500, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('height', 500, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('color', '', function () { return new HexColor(); }, 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true)
+ ->param('background', '', function () { return new HexColor(); }, 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
+ ->label('scope', 'avatars.read')
+ ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'avatars')
+ ->label('sdk.method', 'getInitials')
+ ->label('sdk.methodType', 'location')
+ ->label('sdk.description', '/docs/references/avatars/get-initials.md')
+ ->action(
+ function ($name, $width, $height, $color, $background) use ($response, $user) {
+ $themes = [
+ ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
+ ['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE
+ ['color' => '#006128', 'background' => '#c9f3c6'], // GREEN
+ ['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA
+ ['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE
+ ['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO
+ ['color' => '#610038', 'background' => '#f5d1e6'], // PINK
+ ['color' => '#386100', 'background' => '#dcf1bd'], // LIME
+ ['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
+ ['color' => '#610008', 'background' => '#f6d2d5'] // RED
+ ];
+
+ $rand = \rand(0, \count($themes)-1);
+
+ $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
+ $words = \explode(' ', \strtoupper($name));
+ $initials = null;
+ $code = 0;
+
+ foreach ($words as $key => $w) {
+ $initials .= (isset($w[0])) ? $w[0] : '';
+ $code += (isset($w[0])) ? \ord($w[0]) : 0;
+
+ if($key == 1) {
+ break;
+ }
+ }
+
+ $length = \count($words);
+ $rand = \substr($code,-1);
+ $background = (!empty($background)) ? '#'.$background : $themes[$rand]['background'];
+ $color = (!empty($color)) ? '#'.$color : $themes[$rand]['color'];
+
+ $image = new \Imagick();
+ $draw = new \ImagickDraw();
+ $fontSize = \min($width, $height) / 2;
+
+ $draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
+ $image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
+
+ $draw->setFillColor(new \ImagickPixel($color));
+ $draw->setFontSize($fontSize);
+
+ $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
+ $draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
+
+ $image->newImage($width, $height, $background);
+ $image->setImageFormat("png");
+ $image->drawImage($draw);
+
+ //$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
+
+ $response
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->setContentType('image/png')
+ ->send($image->getImageBlob())
;
}
);
\ No newline at end of file
diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php
index 69d01e9dc7..d8ecedab84 100644
--- a/app/controllers/api/database.php
+++ b/app/controllers/api/database.php
@@ -9,6 +9,7 @@ use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
+use Utopia\Validator\JSON;
use Utopia\Locale\Locale;
use Utopia\Audit\Audit;
use Utopia\Audit\Adapters\MySQL as AuditAdapter;
@@ -26,7 +27,7 @@ use GeoIp2\Database\Reader;
include_once __DIR__ . '/../shared/api.php';
-$isDev = (App::ENV_TYPE_PRODUCTION !== $utopia->getEnv());
+$isDev = (App::MODE_TYPE_PRODUCTION !== $utopia->getMode());
$utopia->post('/v1/database/collections')
->desc('Create Collection')
@@ -45,7 +46,7 @@ $utopia->post('/v1/database/collections')
$parsedRules = [];
foreach ($rules as &$rule) {
- $parsedRules[] = array_merge([
+ $parsedRules[] = \array_merge([
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'$permissions' => [
'read' => $read,
@@ -58,8 +59,8 @@ $utopia->post('/v1/database/collections')
$data = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
'name' => $name,
- 'dateCreated' => time(),
- 'dateUpdated' => time(),
+ 'dateCreated' => \time(),
+ 'dateUpdated' => \time(),
'structure' => true,
'$permissions' => [
'read' => $read,
@@ -169,69 +170,69 @@ $utopia->get('/v1/database/collections/:collectionId')
}
);
-$utopia->get('/v1/database/collections/:collectionId/logs')
- ->desc('Get Collection Logs')
- ->label('scope', 'collections.read')
- ->label('sdk.platform', [APP_PLATFORM_SERVER])
- ->label('sdk.namespace', 'database')
- ->label('sdk.method', 'getCollectionLogs')
- ->label('sdk.description', '/docs/references/database/get-collection-logs.md')
- ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
- ->action(
- function ($collectionId) use ($response, $register, $projectDB, $project) {
- $collection = $projectDB->getDocument($collectionId);
+// $utopia->get('/v1/database/collections/:collectionId/logs')
+// ->desc('Get Collection Logs')
+// ->label('scope', 'collections.read')
+// ->label('sdk.platform', [APP_PLATFORM_SERVER])
+// ->label('sdk.namespace', 'database')
+// ->label('sdk.method', 'getCollectionLogs')
+// ->label('sdk.description', '/docs/references/database/get-collection-logs.md')
+// ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
+// ->action(
+// function ($collectionId) use ($response, $register, $projectDB, $project) {
+// $collection = $projectDB->getDocument($collectionId);
- if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
+// if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+// throw new Exception('Collection not found', 404);
+// }
- $adapter = new AuditAdapter($register->get('db'));
- $adapter->setNamespace('app_'.$project->getId());
+// $adapter = new AuditAdapter($register->get('db'));
+// $adapter->setNamespace('app_'.$project->getId());
- $audit = new Audit($adapter);
+// $audit = new Audit($adapter);
- $countries = Locale::getText('countries');
+// $countries = Locale::getText('countries');
- $logs = $audit->getLogsByResource('database/collection/'.$collection->getId());
+// $logs = $audit->getLogsByResource('database/collection/'.$collection->getId());
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $output = [];
+// $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
+// $output = [];
- foreach ($logs as $i => &$log) {
- $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
+// foreach ($logs as $i => &$log) {
+// $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
- $dd = new DeviceDetector($log['userAgent']);
+// $dd = new DeviceDetector($log['userAgent']);
- $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
+// $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
- $dd->parse();
+// $dd->parse();
- $output[$i] = [
- 'event' => $log['event'],
- 'ip' => $log['ip'],
- 'time' => strtotime($log['time']),
- 'OS' => $dd->getOs(),
- 'client' => $dd->getClient(),
- 'device' => $dd->getDevice(),
- 'brand' => $dd->getBrand(),
- 'model' => $dd->getModel(),
- 'geo' => [],
- ];
+// $output[$i] = [
+// 'event' => $log['event'],
+// 'ip' => $log['ip'],
+// 'time' => strtotime($log['time']),
+// 'OS' => $dd->getOs(),
+// 'client' => $dd->getClient(),
+// 'device' => $dd->getDevice(),
+// 'brand' => $dd->getBrand(),
+// 'model' => $dd->getModel(),
+// 'geo' => [],
+// ];
- try {
- $record = $reader->country($log['ip']);
- $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
- $output[$i]['geo']['country'] = $record->country->name;
- $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- } catch (\Exception $e) {
- $output[$i]['geo']['isoCode'] = '--';
- $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
- }
- }
+// try {
+// $record = $reader->country($log['ip']);
+// $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
+// $output[$i]['geo']['country'] = $record->country->name;
+// $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
+// } catch (\Exception $e) {
+// $output[$i]['geo']['isoCode'] = '--';
+// $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
+// }
+// }
- $response->json($output);
- }
- );
+// $response->json($output);
+// }
+// );
$utopia->put('/v1/database/collections/:collectionId')
->desc('Update Collection')
@@ -257,7 +258,7 @@ $utopia->put('/v1/database/collections/:collectionId')
$parsedRules = [];
foreach ($rules as &$rule) {
- $parsedRules[] = array_merge([
+ $parsedRules[] = \array_merge([
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'$permissions' => [
'read' => $read,
@@ -267,10 +268,10 @@ $utopia->put('/v1/database/collections/:collectionId')
}
try {
- $collection = $projectDB->updateDocument(array_merge($collection->getArrayCopy(), [
+ $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [
'name' => $name,
'structure' => true,
- 'dateUpdated' => time(),
+ 'dateUpdated' => \time(),
'$permissions' => [
'read' => $read,
'write' => $write,
@@ -350,8 +351,8 @@ $utopia->post('/v1/database/collections/:collectionId/documents')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'createDocument')
->label('sdk.description', '/docs/references/database/create-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/database?platform=server#createCollection).')
- ->param('data', [], function () { return new \Utopia\Validator\Mock(); }, 'Document data as JSON object.')
+ ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('data', [], function () { return new JSON(); }, 'Document data as JSON object.')
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('parentDocument', '', function () { return new UID(); }, 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
@@ -359,8 +360,8 @@ $utopia->post('/v1/database/collections/:collectionId/documents')
->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () { return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]); }, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
->action(
function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, $webhook, $audit) {
- $data = (is_string($data) && $result = json_decode($data, true)) ? $result : $data; // Cast to JSON array
-
+ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
+
if (empty($data)) {
throw new Exception('Missing payload', 400);
}
@@ -371,7 +372,7 @@ $utopia->post('/v1/database/collections/:collectionId/documents')
$collection = $projectDB->getDocument($collectionId/*, $isDev*/);
- if (is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
@@ -468,7 +469,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'listDocuments')
->label('sdk.description', '/docs/references/database/list-documents.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/database?platform=server#createCollection).')
+ ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('filters', [], function () { return new ArrayList(new Text(128)); }, 'Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: \'name=John Doe\' or \'category.$id>=5bed2d152c362\'.', true)
->param('offset', 0, function () { return new Range(0, 900000000); }, 'Offset value. Use this value to manage pagination.', true)
->param('limit', 50, function () { return new Range(0, 1000); }, 'Maximum number of documents to return in response. Use this value to manage pagination.', true)
@@ -482,7 +483,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents')
function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search, $first, $last) use ($response, $projectDB, $isDev) {
$collection = $projectDB->getDocument($collectionId, $isDev);
- if (is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
@@ -495,7 +496,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents')
'search' => $search,
'first' => (bool) $first,
'last' => (bool) $last,
- 'filters' => array_merge($filters, [
+ 'filters' => \array_merge($filters, [
'$collection='.$collectionId,
]),
]);
@@ -535,7 +536,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'getDocument')
->label('sdk.description', '/docs/references/database/get-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/database?platform=server#createCollection).')
+ ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
->action(
function ($collectionId, $documentId) use ($response, $request, $projectDB, $isDev) {
@@ -548,20 +549,20 @@ $utopia->get('/v1/database/collections/:collectionId/documents/:documentId')
$output = $document->getArrayCopy();
- $paths = explode('/', $request->getParam('q', ''));
- $paths = array_slice($paths, 7, count($paths));
+ $paths = \explode('/', $request->getParam('q', ''));
+ $paths = \array_slice($paths, 7, \count($paths));
- if (count($paths) > 0) {
- if (count($paths) % 2 == 1) {
- $output = $document->getAttribute(implode('.', $paths));
+ if (\count($paths) > 0) {
+ if (\count($paths) % 2 == 1) {
+ $output = $document->getAttribute(\implode('.', $paths));
} else {
- $id = (int) array_pop($paths);
- $output = $document->search('$id', $id, $document->getAttribute(implode('.', $paths)));
+ $id = (int) \array_pop($paths);
+ $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths)));
}
$output = ($output instanceof Document) ? $output->getArrayCopy() : $output;
- if (!is_array($output)) {
+ if (!\is_array($output)) {
throw new Exception('No document found', 404);
}
}
@@ -581,9 +582,9 @@ $utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'updateDocument')
->label('sdk.description', '/docs/references/database/update-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/database?platform=server#createCollection).')
+ ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
- ->param('data', [], function () { return new \Utopia\Validator\Mock(); }, 'Document data as JSON object.')
+ ->param('data', [], function () { return new JSON(); }, 'Document data as JSON object.')
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->action(
@@ -591,13 +592,13 @@ $utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
$collection = $projectDB->getDocument($collectionId/*, $isDev*/);
$document = $projectDB->getDocument($documentId, $isDev);
- $data = (is_string($data) && $result = json_decode($data, true)) ? $result : $data; // Cast to JSON array
+ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
- if (!is_array($data)) {
+ if (!\is_array($data)) {
throw new Exception('Data param should be a valid JSON', 400);
}
- if (is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
@@ -615,7 +616,7 @@ $utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
$data['$permissions']['write'] = $read;
}
- $data = array_merge($document->getArrayCopy(), $data);
+ $data = \array_merge($document->getArrayCopy(), $data);
$data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID
$data['$id'] = $document->getId(); // Make sure user don't switch document unique ID
@@ -660,7 +661,7 @@ $utopia->delete('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'deleteDocument')
->label('sdk.description', '/docs/references/database/delete-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/database?platform=server#createCollection).')
+ ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
->action(
function ($collectionId, $documentId) use ($response, $projectDB, $audit, $webhook, $isDev) {
@@ -671,7 +672,7 @@ $utopia->delete('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('No document found', 404);
}
- if (is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php
index e2b3c3776f..94c99bc82b 100644
--- a/app/controllers/api/health.php
+++ b/app/controllers/api/health.php
@@ -3,7 +3,7 @@
global $utopia, $request, $response, $register, $project;
use Utopia\Exception;
-use Appwrite\Storage\Devices\Local;
+use Appwrite\Storage\Device\Local;
use Appwrite\Storage\Storage;
use Appwrite\ClamAV\Network;
@@ -20,6 +20,15 @@ $utopia->get('/v1/health')
}
);
+$utopia->get('/v1/health/version')
+ ->desc('Get Version')
+ ->label('scope', 'public')
+ ->action(
+ function () use ($response) {
+ $response->json(['version' => APP_VERSION_STABLE]);
+ }
+ );
+
$utopia->get('/v1/health/db')
->desc('Get DB')
->label('scope', 'health.read')
@@ -66,34 +75,34 @@ $utopia->get('/v1/health/time')
$gap = 60; // Allow [X] seconds gap
/* Create a socket and connect to NTP server */
- $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ $sock = \socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- socket_connect($sock, $host, 123);
+ \socket_connect($sock, $host, 123);
/* Send request */
- $msg = "\010".str_repeat("\0", 47);
+ $msg = "\010".\str_repeat("\0", 47);
- socket_send($sock, $msg, strlen($msg), 0);
+ \socket_send($sock, $msg, \strlen($msg), 0);
/* Receive response and close socket */
- socket_recv($sock, $recv, 48, MSG_WAITALL);
- socket_close($sock);
+ \socket_recv($sock, $recv, 48, MSG_WAITALL);
+ \socket_close($sock);
/* Interpret response */
- $data = unpack('N12', $recv);
- $timestamp = sprintf('%u', $data[9]);
+ $data = \unpack('N12', $recv);
+ $timestamp = \sprintf('%u', $data[9]);
/* NTP is number of seconds since 0000 UT on 1 January 1900
Unix time is seconds since 0000 UT on 1 January 1970 */
$timestamp -= 2208988800;
- $diff = ($timestamp - time());
+ $diff = ($timestamp - \time());
if ($diff > $gap || $diff < ($gap * -1)) {
throw new Exception('Server time gaps detected');
}
- $response->json(['remote' => $timestamp, 'local' => time(), 'diff' => $diff]);
+ $response->json(['remote' => $timestamp, 'local' => \time(), 'diff' => $diff]);
}
);
@@ -124,7 +133,7 @@ $utopia->get('/v1/health/queue/tasks')
);
$utopia->get('/v1/health/queue/logs')
-->desc('Get Logs Queue')
+ ->desc('Get Logs Queue')
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.namespace', 'health')
@@ -184,26 +193,22 @@ $utopia->get('/v1/health/storage/local')
->label('sdk.description', '/docs/references/health/get-storage-local.md')
->action(
function () use ($response) {
- $device = new Local(APP_STORAGE_UPLOADS.'/');
- if (!is_readable($device->getRoot().'/..')) {
- throw new Exception('Device is not readable');
- }
-
- if (!is_writable($device->getRoot().'/../uploads')) {
- throw new Exception('Device uploads dir is not writable');
- }
-
- if (!is_writable($device->getRoot().'/../cache')) {
- throw new Exception('Device cache dir is not writable');
- }
+ foreach ([
+ 'Uploads' => APP_STORAGE_UPLOADS,
+ 'Cache' => APP_STORAGE_CACHE,
+ 'Config' => APP_STORAGE_CONFIG,
+ 'Certs' => APP_STORAGE_CERTIFICATES
+ ] as $key => $volume) {
+ $device = new Local($volume);
- if (!is_writable($device->getRoot().'/../config')) {
- throw new Exception('Device config dir is not writable');
- }
+ if (!\is_readable($device->getRoot())) {
+ throw new Exception('Device '.$key.' dir is not readable');
+ }
- if (!is_writable($device->getRoot().'/../certificates')) {
- throw new Exception('Device certificates dir is not writable');
+ if (!\is_writable($device->getRoot())) {
+ throw new Exception('Device '.$key.' dir is not writable');
+ }
}
$response->json(['status' => 'OK']);
@@ -250,7 +255,7 @@ $utopia->get('/v1/health/stats') // Currently only used internally
->json([
'server' => [
'name' => 'nginx',
- 'version' => shell_exec('nginx -v 2>&1'),
+ 'version' => \shell_exec('nginx -v 2>&1'),
],
'storage' => [
'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')),
diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php
index 8f4f2e3272..30a1211e91 100644
--- a/app/controllers/api/locale.php
+++ b/app/controllers/api/locale.php
@@ -26,7 +26,7 @@ $utopia->get('/v1/locale')
$countries = Locale::getText('countries');
$continents = Locale::getText('continents');
- if (App::ENV_TYPE_PRODUCTION !== $utopia->getEnv()) {
+ if (App::MODE_TYPE_PRODUCTION !== $utopia->getMode()) {
$ip = '79.177.241.94';
}
@@ -41,10 +41,10 @@ $utopia->get('/v1/locale')
//$output['countryTimeZone'] = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $record->country->isoCode);
$output['continent'] = (isset($continents[$record->continent->code])) ? $continents[$record->continent->code] : Locale::getText('locale.country.unknown');
$output['continentCode'] = $record->continent->code;
- $output['eu'] = (in_array($record->country->isoCode, $eu)) ? true : false;
+ $output['eu'] = (\in_array($record->country->isoCode, $eu)) ? true : false;
foreach ($currencies as $code => $element) {
- if (isset($element['locations']) && isset($element['code']) && in_array($record->country->isoCode, $element['locations'])) {
+ if (isset($element['locations']) && isset($element['code']) && \in_array($record->country->isoCode, $element['locations'])) {
$currency = $element['code'];
}
}
@@ -61,7 +61,7 @@ $utopia->get('/v1/locale')
$response
->addHeader('Cache-Control', 'public, max-age='.$time)
- ->addHeader('Expires', date('D, d M Y H:i:s', time() + $time).' GMT') // 45 days cache
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
->json($output);
}
);
@@ -77,7 +77,7 @@ $utopia->get('/v1/locale/countries')
function () use ($response) {
$list = Locale::getText('countries'); /* @var $list array */
- asort($list);
+ \asort($list);
$response->json($list);
}
@@ -97,12 +97,12 @@ $utopia->get('/v1/locale/countries/eu')
$list = [];
foreach ($eu as $code) {
- if (array_key_exists($code, $countries)) {
+ if (\array_key_exists($code, $countries)) {
$list[$code] = $countries[$code];
}
}
- asort($list);
+ \asort($list);
$response->json($list);
}
@@ -122,12 +122,12 @@ $utopia->get('/v1/locale/countries/phones')
$countries = Locale::getText('countries'); /* @var $countries array */
foreach ($list as $code => $name) {
- if (array_key_exists($code, $countries)) {
+ if (\array_key_exists($code, $countries)) {
$list[$code] = '+'.$list[$code];
}
}
- asort($list);
+ \asort($list);
$response->json($list);
}
@@ -144,7 +144,7 @@ $utopia->get('/v1/locale/continents')
function () use ($response) {
$list = Locale::getText('continents'); /* @var $list array */
- asort($list);
+ \asort($list);
$response->json($list);
}
@@ -165,3 +165,19 @@ $utopia->get('/v1/locale/currencies')
$response->json($currencies);
}
);
+
+
+$utopia->get('/v1/locale/languages')
+ ->desc('List Languages')
+ ->label('scope', 'locale.read')
+ ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'locale')
+ ->label('sdk.method', 'getLanguages')
+ ->label('sdk.description', '/docs/references/locale/get-languages.md')
+ ->action(
+ function () use ($response) {
+ $languages = include __DIR__.'/../../config/languages.php';
+
+ $response->json($languages);
+ }
+ );
\ No newline at end of file
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index 2ffc3a0233..4511c34807 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -18,7 +18,7 @@ use Appwrite\Database\Database;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\UID;
use Appwrite\OpenSSL\OpenSSL;
-use Appwrite\Network\Validators\CNAME;
+use Appwrite\Network\Validator\CNAME;
use Cron\CronExpression;
include_once __DIR__ . '/../shared/api.php';
@@ -107,11 +107,11 @@ $utopia->get('/v1/projects')
foreach ($results as $project) {
foreach (Config::getParam('providers') as $provider => $node) {
- $secret = json_decode($project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'), true);
+ $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true);
if (!empty($secret) && isset($secret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']);
- $project->setAttribute('usersOauth2'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag'])));
+ $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag'])));
}
}
}
@@ -135,11 +135,11 @@ $utopia->get('/v1/projects/:projectId')
}
foreach (Config::getParam('providers') as $provider => $node) {
- $secret = json_decode($project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'), true);
+ $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true);
if (!empty($secret) && isset($secret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']);
- $project->setAttribute('usersOauth2'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag'])));
+ $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag'])));
}
}
@@ -164,23 +164,23 @@ $utopia->get('/v1/projects/:projectId/usage')
$period = [
'daily' => [
- 'start' => DateTime::createFromFormat('U', strtotime('today')),
- 'end' => DateTime::createFromFormat('U', strtotime('tomorrow')),
+ 'start' => DateTime::createFromFormat('U', \strtotime('today')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
'group' => '1m',
],
'monthly' => [
- 'start' => DateTime::createFromFormat('U', strtotime('midnight first day of this month')),
- 'end' => DateTime::createFromFormat('U', strtotime('midnight last day of this month')),
+ 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')),
'group' => '1d',
],
'last30' => [
- 'start' => DateTime::createFromFormat('U', strtotime('-30 days')),
- 'end' => DateTime::createFromFormat('U', strtotime('tomorrow')),
+ 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
'group' => '1d',
],
'last90' => [
- 'start' => DateTime::createFromFormat('U', strtotime('-90 days')),
- 'end' => DateTime::createFromFormat('U', strtotime('today')),
+ 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('today')),
'group' => '1d',
],
// 'yearly' => [
@@ -207,7 +207,7 @@ $utopia->get('/v1/projects/:projectId/usage')
foreach ($points as $point) {
$requests[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => strtotime($point['time']),
+ 'date' => \strtotime($point['time']),
];
}
@@ -218,7 +218,7 @@ $utopia->get('/v1/projects/:projectId/usage')
foreach ($points as $point) {
$network[] = [
'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => strtotime($point['time']),
+ 'date' => \strtotime($point['time']),
];
}
}
@@ -262,18 +262,18 @@ $utopia->get('/v1/projects/:projectId/usage')
}
// Tasks
- $tasksTotal = count($project->getAttribute('tasks', []));
+ $tasksTotal = \count($project->getAttribute('tasks', []));
$response->json([
'requests' => [
'data' => $requests,
- 'total' => array_sum(array_map(function ($item) {
+ 'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $requests)),
],
'network' => [
'data' => $network,
- 'total' => array_sum(array_map(function ($item) {
+ 'total' => \array_sum(\array_map(function ($item) {
return $item['value'];
}, $network)),
],
@@ -283,7 +283,7 @@ $utopia->get('/v1/projects/:projectId/usage')
],
'documents' => [
'data' => $documents,
- 'total' => array_sum(array_map(function ($item) {
+ 'total' => \array_sum(\array_map(function ($item) {
return $item['total'];
}, $documents)),
],
@@ -332,7 +332,7 @@ $utopia->patch('/v1/projects/:projectId')
throw new Exception('Project not found', 404);
}
- $project = $consoleDB->updateDocument(array_merge($project->getArrayCopy(), [
+ $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
'name' => $name,
'description' => $description,
'logo' => $logo,
@@ -359,7 +359,7 @@ $utopia->patch('/v1/projects/:projectId/oauth2')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateOAuth2')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'Provider Name', false)
+ ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'Provider Name', false)
->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true)
->param('secret', '', function () { return new text(512); }, 'Provider secret key.', true)
->action(
@@ -373,17 +373,17 @@ $utopia->patch('/v1/projects/:projectId/oauth2')
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
- $secret = json_encode([
+ $secret = \json_encode([
'data' => OpenSSL::encrypt($secret, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => bin2hex($iv),
- 'tag' => bin2hex($tag),
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag),
'version' => '1',
]);
- $project = $consoleDB->updateDocument(array_merge($project->getArrayCopy(), [
- 'usersOauth2'.ucfirst($provider).'Appid' => $appId,
- 'usersOauth2'.ucfirst($provider).'Secret' => $secret,
+ $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
+ 'usersOauth2'.\ucfirst($provider).'Appid' => $appId,
+ 'usersOauth2'.\ucfirst($provider).'Secret' => $secret,
]));
if (false === $project) {
@@ -424,6 +424,10 @@ $utopia->delete('/v1/projects/:projectId')
}
}
}
+
+ if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) {
+ throw new Exception('Failed to remove project team from DB', 500);
+ }
if (!$consoleDB->deleteDocument($projectId)) {
throw new Exception('Failed to remove project from DB', 500);
@@ -458,11 +462,11 @@ $utopia->post('/v1/projects/:projectId/webhooks')
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
- $httpPass = json_encode([
+ $httpPass = \json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => bin2hex($iv),
- 'tag' => bin2hex($tag),
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag),
'version' => '1',
]);
@@ -516,7 +520,7 @@ $utopia->get('/v1/projects/:projectId/webhooks')
$webhooks = $project->getAttribute('webhooks', []);
foreach ($webhooks as $webhook) { /* @var $webhook Document */
- $httpPass = json_decode($webhook->getAttribute('httpPass', '{}'), true);
+ $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true);
if (empty($httpPass) || !isset($httpPass['version'])) {
continue;
@@ -524,7 +528,7 @@ $utopia->get('/v1/projects/:projectId/webhooks')
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
+ $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
}
$response->json($webhooks);
@@ -552,11 +556,11 @@ $utopia->get('/v1/projects/:projectId/webhooks/:webhookId')
throw new Exception('Webhook not found', 404);
}
- $httpPass = json_decode($webhook->getAttribute('httpPass', '{}'), true);
+ $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true);
if (!empty($httpPass) && isset($httpPass['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
+ $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
}
$response->json($webhook->getArrayCopy());
@@ -588,11 +592,11 @@ $utopia->put('/v1/projects/:projectId/webhooks/:webhookId')
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
- $httpPass = json_encode([
+ $httpPass = \json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => bin2hex($iv),
- 'tag' => bin2hex($tag),
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag),
'version' => '1',
]);
@@ -674,7 +678,7 @@ $utopia->post('/v1/projects/:projectId/keys')
],
'name' => $name,
'scopes' => $scopes,
- 'secret' => bin2hex(random_bytes(128)),
+ 'secret' => \bin2hex(\random_bytes(128)),
]);
if (false === $key) {
@@ -835,11 +839,11 @@ $utopia->post('/v1/projects/:projectId/tasks')
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
- $httpPass = json_encode([
+ $httpPass = \json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => bin2hex($iv),
- 'tag' => bin2hex($tag),
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag),
'version' => '1',
]);
@@ -852,7 +856,7 @@ $utopia->post('/v1/projects/:projectId/tasks')
'name' => $name,
'status' => $status,
'schedule' => $schedule,
- 'updated' => time(),
+ 'updated' => \time(),
'previous' => null,
'next' => $next,
'security' => (int) $security,
@@ -905,7 +909,7 @@ $utopia->get('/v1/projects/:projectId/tasks')
$tasks = $project->getAttribute('tasks', []);
foreach ($tasks as $task) { /* @var $task Document */
- $httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
+ $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true);
if (empty($httpPass) || !isset($httpPass['version'])) {
continue;
@@ -913,7 +917,7 @@ $utopia->get('/v1/projects/:projectId/tasks')
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
+ $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
}
$response->json($tasks);
@@ -941,11 +945,11 @@ $utopia->get('/v1/projects/:projectId/tasks/:taskId')
throw new Exception('Task not found', 404);
}
- $httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
+ $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true);
if (!empty($httpPass) && isset($httpPass['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
+ $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
}
$response->json($task->getArrayCopy());
@@ -988,11 +992,11 @@ $utopia->put('/v1/projects/:projectId/tasks/:taskId')
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
- $httpPass = json_encode([
+ $httpPass = \json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => bin2hex($iv),
- 'tag' => bin2hex($tag),
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag),
'version' => '1',
]);
@@ -1000,7 +1004,7 @@ $utopia->put('/v1/projects/:projectId/tasks/:taskId')
->setAttribute('name', $name)
->setAttribute('status', $status)
->setAttribute('schedule', $schedule)
- ->setAttribute('updated', time())
+ ->setAttribute('updated', \time())
->setAttribute('next', $next)
->setAttribute('security', (int) $security)
->setAttribute('httpMethod', $httpMethod)
@@ -1083,8 +1087,8 @@ $utopia->post('/v1/projects/:projectId/platforms')
'key' => $key,
'store' => $store,
'hostname' => $hostname,
- 'dateCreated' => time(),
- 'dateUpdated' => time(),
+ 'dateCreated' => \time(),
+ 'dateUpdated' => \time(),
]);
if (false === $platform) {
@@ -1178,7 +1182,7 @@ $utopia->put('/v1/projects/:projectId/platforms/:platformId')
$platform
->setAttribute('name', $name)
- ->setAttribute('dateUpdated', time())
+ ->setAttribute('dateUpdated', \time())
->setAttribute('key', $key)
->setAttribute('store', $store)
->setAttribute('hostname', $hostname)
@@ -1258,7 +1262,7 @@ $utopia->post('/v1/projects/:projectId/domains')
'read' => ['team:'.$project->getAttribute('teamId', null)],
'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
],
- 'updated' => time(),
+ 'updated' => \time(),
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 18a8c98c26..6ffcfbf985 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -15,10 +15,10 @@ use Appwrite\ClamAV\Network;
use Appwrite\Database\Database;
use Appwrite\Database\Validator\UID;
use Appwrite\Storage\Storage;
-use Appwrite\Storage\Devices\Local;
-use Appwrite\Storage\Validators\File;
-use Appwrite\Storage\Validators\FileSize;
-use Appwrite\Storage\Validators\Upload;
+use Appwrite\Storage\Device\Local;
+use Appwrite\Storage\Validator\File;
+use Appwrite\Storage\Validator\FileSize;
+use Appwrite\Storage\Validator\Upload;
use Appwrite\Storage\Compression\Algorithms\GZIP;
use Appwrite\Resize\Resize;
use Appwrite\OpenSSL\OpenSSL;
@@ -165,9 +165,9 @@ $utopia->post('/v1/storage/files')
}
// Make sure we handle a single file and multiple files the same way
- $file['name'] = (is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
- $file['tmp_name'] = (is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
- $file['size'] = (is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
+ $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
+ $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
+ $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
// Check if file type is allowed (feature for project settings?)
//if (!$fileType->isValid($file['tmp_name'])) {
@@ -190,7 +190,7 @@ $utopia->post('/v1/storage/files')
// Save to storage
$size = $device->getFileSize($file['tmp_name']);
- $path = $device->getPath(uniqid().'.'.pathinfo($file['name'], PATHINFO_EXTENSION));
+ $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION));
if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
throw new Exception('Failed moving file', 500);
@@ -228,7 +228,7 @@ $utopia->post('/v1/storage/files')
'read' => $read,
'write' => $write,
],
- 'dateCreated' => time(),
+ 'dateCreated' => \time(),
'folderId' => $folderId,
'name' => $file['name'],
'path' => $path,
@@ -237,12 +237,12 @@ $utopia->post('/v1/storage/files')
'sizeOriginal' => $size,
'sizeActual' => $sizeActual,
'algorithm' => $compressor->getName(),
- 'token' => bin2hex(random_bytes(64)),
+ 'token' => \bin2hex(\random_bytes(64)),
'comment' => '',
'fileOpenSSLVersion' => '1',
'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
- 'fileOpenSSLTag' => bin2hex($tag),
- 'fileOpenSSLIV' => bin2hex($iv),
+ 'fileOpenSSLTag' => \bin2hex($tag),
+ 'fileOpenSSLIV' => \bin2hex($iv),
]);
if (false === $file) {
@@ -294,7 +294,7 @@ $utopia->get('/v1/storage/files')
],
]);
- $results = array_map(function ($value) { /* @var $value \Database\Document */
+ $results = \array_map(function ($value) { /* @var $value \Database\Document */
return $value->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']);
}, $results);
@@ -336,14 +336,14 @@ $utopia->get('/v1/storage/files/:fileId/preview')
->param('height', 0, function () { return new Range(0, 4000); }, 'Resize preview image height, Pass an integer between 0 to 4000.', true)
->param('quality', 100, function () { return new Range(0, 100); }, 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->param('background', '', function () { return new HexColor(); }, 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
- ->param('output', null, function () use ($outputs) { return new WhiteList(array_merge(array_keys($outputs), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true)
+ ->param('output', null, function () use ($outputs) { return new WhiteList(\array_merge(\array_keys($outputs), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true)
//->param('storage', 'local', function () {return new WhiteList(array('local'));}, 'Selected storage device. defaults to local')
//->param('token', '', function () {return new Text(128);}, 'Preview token', true)
->action(
function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs, $fileLogos) {
$storage = 'local';
- if (!extension_loaded('imagick')) {
+ if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500);
}
@@ -351,12 +351,12 @@ $utopia->get('/v1/storage/files/:fileId/preview')
throw new Exception('No such storage device', 400);
}
- if ((strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
+ if ((\strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
- $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = md5($fileId.$width.$height.$quality.$background.$storage.$output);
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output);
$file = $projectDB->getDocument($fileId);
@@ -365,24 +365,24 @@ $utopia->get('/v1/storage/files/:fileId/preview')
}
$path = $file->getAttribute('path');
- $type = strtolower(pathinfo($path, PATHINFO_EXTENSION));
+ $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$algorithm = $file->getAttribute('algorithm');
$cipher = $file->getAttribute('fileOpenSSLCipher');
$mime = $file->getAttribute('mimeType');
- if(!in_array($mime, $inputs)) {
- $path = (array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
+ if(!\in_array($mime, $inputs)) {
+ $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
$algorithm = null;
$cipher = null;
$background = (empty($background)) ? 'eceff1' : $background;
- $type = strtolower(pathinfo($path, PATHINFO_EXTENSION));
- $key = md5($path.$width.$height.$quality.$background.$storage.$output);
+ $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
+ $key = \md5($path.$width.$height.$quality.$background.$storage.$output);
}
$compressor = new GZIP();
$device = Storage::getDevice('local');
- if (!file_exists($path)) {
+ if (!\file_exists($path)) {
throw new Exception('File not found', 404);
}
@@ -393,7 +393,7 @@ $utopia->get('/v1/storage/files/:fileId/preview')
$output = (empty($output)) ? $type : $output;
$response
- ->setContentType((in_array($output, $outputs)) ? $outputs[$output] : $outputs['jpg'])
+ ->setContentType((\in_array($output, $outputs)) ? $outputs[$output] : $outputs['jpg'])
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
->send($data)
@@ -410,8 +410,8 @@ $utopia->get('/v1/storage/files/:fileId/preview')
$file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0,
- hex2bin($file->getAttribute('fileOpenSSLIV')),
- hex2bin($file->getAttribute('fileOpenSSLTag'))
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
);
}
@@ -466,7 +466,7 @@ $utopia->get('/v1/storage/files/:fileId/download')
$path = $file->getAttribute('path', '');
- if (!file_exists($path)) {
+ if (!\file_exists($path)) {
throw new Exception('File not found in '.$path, 404);
}
@@ -481,8 +481,8 @@ $utopia->get('/v1/storage/files/:fileId/download')
$file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0,
- hex2bin($file->getAttribute('fileOpenSSLIV')),
- hex2bin($file->getAttribute('fileOpenSSLTag'))
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
);
}
@@ -492,8 +492,8 @@ $utopia->get('/v1/storage/files/:fileId/download')
$response
->setContentType($file->getAttribute('mimeType'))
->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"')
- ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->addHeader('X-Peak', memory_get_peak_usage())
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->addHeader('X-Peak', \memory_get_peak_usage())
->send($source)
;
}
@@ -520,7 +520,7 @@ $utopia->get('/v1/storage/files/:fileId/view')
$path = $file->getAttribute('path', '');
- if (!file_exists($path)) {
+ if (!\file_exists($path)) {
throw new Exception('File not found in '.$path, 404);
}
@@ -529,7 +529,7 @@ $utopia->get('/v1/storage/files/:fileId/view')
$contentType = 'text/plain';
- if (in_array($file->getAttribute('mimeType'), $mimes)) {
+ if (\in_array($file->getAttribute('mimeType'), $mimes)) {
$contentType = $file->getAttribute('mimeType');
}
@@ -541,8 +541,8 @@ $utopia->get('/v1/storage/files/:fileId/view')
$file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0,
- hex2bin($file->getAttribute('fileOpenSSLIV')),
- hex2bin($file->getAttribute('fileOpenSSLTag'))
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
);
}
@@ -554,7 +554,7 @@ $utopia->get('/v1/storage/files/:fileId/view')
'text' => 'text/plain',
];
- $contentType = (array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType;
+ $contentType = (\array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType;
// Response
$response
@@ -562,8 +562,8 @@ $utopia->get('/v1/storage/files/:fileId/view')
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"')
- ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->addHeader('X-Peak', memory_get_peak_usage())
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->addHeader('X-Peak', \memory_get_peak_usage())
->send($output)
;
}
@@ -589,7 +589,7 @@ $utopia->put('/v1/storage/files/:fileId')
throw new Exception('File not found', 404);
}
- $file = $projectDB->updateDocument(array_merge($file->getArrayCopy(), [
+ $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [
'$permissions' => [
'read' => $read,
'write' => $write,
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index fad09b695e..b8e0bf1194 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -1,6 +1,6 @@
post('/v1/teams')
],
'name' => $name,
'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0,
- 'dateCreated' => time(),
+ 'dateCreated' => \time(),
]);
Authorization::reset();
@@ -62,8 +62,8 @@ $utopia->post('/v1/teams')
'userId' => $user->getId(),
'teamId' => $team->getId(),
'roles' => $roles,
- 'invited' => time(),
- 'joined' => time(),
+ 'invited' => \time(),
+ 'joined' => \time(),
'confirm' => true,
'secret' => '',
]);
@@ -151,7 +151,7 @@ $utopia->put('/v1/teams/:teamId')
throw new Exception('Team not found', 404);
}
- $team = $projectDB->updateDocument(array_merge($team->getArrayCopy(), [
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
'name' => $name,
]));
@@ -215,7 +215,7 @@ $utopia->post('/v1/teams/:teamId/memberships')
->param('roles', [], function () { return new ArrayList(new Text(128)); }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions).')
->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.') // TODO add our own built-in confirm page
->action(
- function ($teamId, $email, $name, $roles, $url) use ($response, $register, $project, $user, $audit, $projectDB) {
+ function ($teamId, $email, $name, $roles, $url) use ($response, $mail, $project, $user, $audit, $projectDB, $mode) {
$name = (empty($name)) ? $email : $name;
$team = $projectDB->getDocument($teamId);
@@ -256,8 +256,8 @@ $utopia->post('/v1/teams/:teamId/memberships')
'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => Auth::passwordHash(Auth::passwordGenerator()),
- 'password-update' => time(),
- 'registration' => time(),
+ 'password-update' => \time(),
+ 'registration' => \time(),
'reset' => false,
'name' => $name,
'tokens' => [],
@@ -280,12 +280,12 @@ $utopia->post('/v1/teams/:teamId/memberships')
throw new Exception('User has already been invited or is already a member of this team', 409);
}
- if ($member->getAttribute('userId') == $user->getId() && in_array('owner', $member->getAttribute('roles', []))) {
+ if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) {
$isOwner = true;
}
}
- if (!$isOwner) {
+ if (!$isOwner && (APP_MODE_ADMIN !== $mode)) {
throw new Exception('User is not allowed to send invitations for this team', 401);
}
@@ -300,13 +300,20 @@ $utopia->post('/v1/teams/:teamId/memberships')
'userId' => $invitee->getId(),
'teamId' => $team->getId(),
'roles' => $roles,
- 'invited' => time(),
+ 'invited' => \time(),
'joined' => 0,
- 'confirm' => false,
+ 'confirm' => (APP_MODE_ADMIN === $mode),
'secret' => Auth::hash($secret),
]);
- $membership = $projectDB->createDocument($membership->getArrayCopy());
+ if(APP_MODE_ADMIN === $mode) { // Allow admin to create membership
+ Authorization::disable();
+ $membership = $projectDB->createDocument($membership->getArrayCopy());
+ Authorization::reset();
+ }
+ else {
+ $membership = $projectDB->createDocument($membership->getArrayCopy());
+ }
if (false === $membership) {
throw new Exception('Failed saving membership to DB', 500);
@@ -316,27 +323,36 @@ $utopia->post('/v1/teams/:teamId/memberships')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
$url = Template::unParseURL($url);
- $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.invitation.body'));
+ $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
+ $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.invitation.body'));
+ $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
+
$body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', Locale::getText('account.emails.invitation.title'))
->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
->setParam('{{team}}', $team->getAttribute('name', '[TEAM-NAME]'))
->setParam('{{owner}}', $user->getAttribute('name', ''))
->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
;
- $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
-
- $mail->addAddress($email, $name);
-
- $mail->Subject = sprintf(Locale::getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]']));
- $mail->Body = $body->render();
- $mail->AltBody = strip_tags($body->render());
-
- try {
- $mail->send();
- } catch (\Exception $error) {
- throw new Exception('Error sending mail: ' . $error->getMessage(), 500);
+ if(APP_MODE_ADMIN !== $mode) { // No need in comfirmation when in admin mode
+ $mail
+ ->setParam('event', 'teams.membership.create')
+ ->setParam('recipient', $email)
+ ->setParam('name', $name)
+ ->setParam('subject', \sprintf(Locale::getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]'])))
+ ->setParam('body', $body->render())
+ ->trigger();
+ ;
}
$audit
@@ -347,7 +363,7 @@ $utopia->post('/v1/teams/:teamId/memberships')
$response
->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint
- ->json(array_merge($membership->getArrayCopy([
+ ->json(\array_merge($membership->getArrayCopy([
'$id',
'userId',
'teamId',
@@ -371,8 +387,12 @@ $utopia->get('/v1/teams/:teamId/memberships')
->label('sdk.method', 'getMemberships')
->label('sdk.description', '/docs/references/teams/get-team-members.md')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
+ ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
+ ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
->action(
- function ($teamId) use ($response, $projectDB) {
+ function ($teamId, $search, $limit, $offset, $orderType) use ($response, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
@@ -380,8 +400,12 @@ $utopia->get('/v1/teams/:teamId/memberships')
}
$memberships = $projectDB->getCollection([
- 'limit' => 50,
- 'offset' => 0,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'joined',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId='.$teamId,
@@ -397,7 +421,7 @@ $utopia->get('/v1/teams/:teamId/memberships')
$temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']);
- $users[] = array_merge($temp, $membership->getArrayCopy([
+ $users[] = \array_merge($temp, $membership->getArrayCopy([
'$id',
'userId',
'teamId',
@@ -408,15 +432,8 @@ $utopia->get('/v1/teams/:teamId/memberships')
]));
}
- usort($users, function ($a, $b) {
- if ($a['joined'] === 0 || $b['joined'] === 0) {
- return $b['joined'] - $a['joined'];
- }
+ $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]);
- return $a['joined'] - $b['joined'];
- });
-
- $response->json($users);
}
);
@@ -478,7 +495,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
}
$membership // Attach user to team
- ->setAttribute('joined', time())
+ ->setAttribute('joined', \time())
->setAttribute('confirm', true)
;
@@ -488,7 +505,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
;
// Log user in
- $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$secret = Auth::tokenGenerator();
$user->setAttribute('tokens', new Document([
@@ -511,7 +528,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
Authorization::disable();
- $team = $projectDB->updateDocument(array_merge($team->getArrayCopy(), [
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
'sum' => $team->getAttribute('sum', 0) + 1,
]));
@@ -529,14 +546,14 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
if(!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$response
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ->json(array_merge($membership->getArrayCopy([
+ ->json(\array_merge($membership->getArrayCopy([
'$id',
'userId',
'teamId',
@@ -583,9 +600,11 @@ $utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
throw new Exception('Failed to remove membership from DB', 500);
}
- $team = $projectDB->updateDocument(array_merge($team->getArrayCopy(), [
- 'sum' => $team->getAttribute('sum', 0) - 1,
- ]));
+ if ($membership->getAttribute('confirm')) { // Count only confirmed members
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
+ 'sum' => $team->getAttribute('sum', 0) - 1,
+ ]));
+ }
if (false === $team) {
throw new Exception('Failed saving team to DB', 500);
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index 12090ecc66..d032cef947 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -59,8 +59,8 @@ $utopia->post('/v1/users')
'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => Auth::passwordHash($password),
- 'password-update' => time(),
- 'registration' => time(),
+ 'password-update' => \time(),
+ 'registration' => \time(),
'reset' => false,
'name' => $name,
], ['email' => $email]);
@@ -75,13 +75,13 @@ $utopia->post('/v1/users')
continue;
}
- $oauth2Keys[] = 'oauth2'.ucfirst($key);
- $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken';
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json(array_merge($user->getArrayCopy(array_merge([
+ ->json(\array_merge($user->getArrayCopy(\array_merge([
'$id',
'status',
'email',
@@ -124,12 +124,12 @@ $utopia->get('/v1/users')
continue;
}
- $oauth2Keys[] = 'oauth2'.ucfirst($key);
- $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken';
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
- $results = array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */
- return $value->getArrayCopy(array_merge(
+ $results = \array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */
+ return $value->getArrayCopy(\array_merge(
[
'$id',
'status',
@@ -169,11 +169,11 @@ $utopia->get('/v1/users/:userId')
continue;
}
- $oauth2Keys[] = 'oauth2'.ucfirst($key);
- $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken';
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
- $response->json(array_merge($user->getArrayCopy(array_merge(
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
[
'$id',
'status',
@@ -206,7 +206,7 @@ $utopia->get('/v1/users/:userId/prefs')
$prefs = $user->getAttribute('prefs', '');
try {
- $prefs = json_decode($prefs, true);
+ $prefs = \json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
@@ -265,7 +265,7 @@ $utopia->get('/v1/users/:userId/sessions')
try {
$record = $reader->country($token->getAttribute('ip', ''));
- $sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
+ $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--';
@@ -335,7 +335,7 @@ $utopia->get('/v1/users/:userId/logs')
$output[$i] = [
'event' => $log['event'],
'ip' => $log['ip'],
- 'time' => strtotime($log['time']),
+ 'time' => \strtotime($log['time']),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
@@ -346,7 +346,7 @@ $utopia->get('/v1/users/:userId/logs')
try {
$record = $reader->country($log['ip']);
- $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
+ $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
@@ -376,7 +376,7 @@ $utopia->patch('/v1/users/:userId/status')
throw new Exception('User not found', 404);
}
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'status' => (int)$status,
]));
@@ -391,12 +391,12 @@ $utopia->patch('/v1/users/:userId/status')
continue;
}
- $oauth2Keys[] = 'oauth2'.ucfirst($key);
- $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken';
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
$response
- ->json(array_merge($user->getArrayCopy(array_merge([
+ ->json(\array_merge($user->getArrayCopy(\array_merge([
'$id',
'status',
'email',
@@ -424,11 +424,11 @@ $utopia->patch('/v1/users/:userId/prefs')
throw new Exception('User not found', 404);
}
- $old = json_decode($user->getAttribute('prefs', '{}'), true);
+ $old = \json_decode($user->getAttribute('prefs', '{}'), true);
$old = ($old) ? $old : [];
- $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
- 'prefs' => json_encode(array_merge($old, $prefs)),
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'prefs' => \json_encode(\array_merge($old, $prefs)),
]));
if (false === $user) {
@@ -438,7 +438,7 @@ $utopia->patch('/v1/users/:userId/prefs')
$prefs = $user->getAttribute('prefs', '');
try {
- $prefs = json_decode($prefs, true);
+ $prefs = \json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
diff --git a/app/controllers/mock.php b/app/controllers/mock.php
index 0a75d85d73..67e0776a90 100644
--- a/app/controllers/mock.php
+++ b/app/controllers/mock.php
@@ -7,7 +7,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
use Utopia\Response;
use Utopia\Validator\Host;
-use Appwrite\Storage\Validators\File;
+use Appwrite\Storage\Validator\File;
$result = [];
@@ -175,9 +175,9 @@ $utopia->post('/v1/mock/tests/general/upload')
->action(
function ($x, $y, $z, $file) use ($request) {
$file = $request->getFiles('file');
- $file['tmp_name'] = (is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']];
- $file['name'] = (is_array($file['name'])) ? $file['name'] : [$file['name']];
- $file['size'] = (is_array($file['size'])) ? $file['size'] : [$file['size']];
+ $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']];
+ $file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']];
+ $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']];
foreach ($file['name'] as $i => $name) {
if($name !== 'file.png') {
@@ -192,7 +192,7 @@ $utopia->post('/v1/mock/tests/general/upload')
}
foreach ($file['tmp_name'] as $i => $tmpName) {
- if(md5(file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
+ if(\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
throw new Exception('Wrong file uploaded', 400);
}
}
@@ -230,7 +230,7 @@ $utopia->get('/v1/mock/tests/general/set-cookie')
->label('sdk.description', 'Mock a set cookie request for SDK tests')
->action(
function () use ($response) {
- $response->addCookie('cookieName', 'cookieValue', time() + 31536000, '/', 'localhost', true, true);
+ $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', 'localhost', true, true);
}
);
@@ -271,7 +271,7 @@ $utopia->get('/v1/mock/tests/general/oauth2')
->param('state', '', function () { return new Text(1024); }, 'OAuth2 state.')
->action(
function ($clientId, $redirectURI, $scope, $state) use ($response) {
- $response->redirect($redirectURI.'?'.http_build_query(['code' => 'abcdef', 'state' => $state]));
+ $response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state]));
}
);
@@ -348,17 +348,17 @@ $utopia->shutdown(function() use ($response, $request, &$result, $utopia) {
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE.'/tests.json';
- $tests = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
- if(!is_array($tests)) {
+ if(!\is_array($tests)) {
throw new Exception('Failed to read results', 500);
}
$result[$route->getMethod() . ':' . $route->getURL()] = true;
- $tests = array_merge($tests, $result);
+ $tests = \array_merge($tests, $result);
- if(!file_put_contents($path, json_encode($tests), LOCK_EX)) {
+ if(!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
throw new Exception('Failed to save resutls', 500);
}
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index 0430212be7..97da51031d 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -10,6 +10,10 @@ $utopia->init(function () use ($utopia, $request, $response, $register, $user, $
$route = $utopia->match($request);
+ if(empty($project->getId()) && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
+ throw new Exception('Missing or unknown project ID', 400);
+ }
+
/*
* Abuse Check
*/
@@ -27,12 +31,13 @@ $utopia->init(function () use ($utopia, $request, $response, $register, $user, $
//TODO make sure we get array here
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
- $timeLimit->setParam('{param-'.$key.'}', (is_array($value)) ? json_encode($value) : $value);
+ $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit()) {
+
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php
index 42afb283e6..fced2de4db 100644
--- a/app/controllers/shared/web.php
+++ b/app/controllers/shared/web.php
@@ -28,16 +28,16 @@ $layout
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', $roles)
- ->setParam('env', $utopia->getEnv())
+ ->setParam('env', $utopia->getMode())
;
$utopia->init(function () use ($utopia, $response, $request, $layout) {
$time = (60 * 60 * 24 * 45); // 45 days cache
- $isDev = (\Utopia\App::ENV_TYPE_DEVELOPMENT == Config::getParam('env'));
+ $isDev = (\Utopia\App::MODE_TYPE_DEVELOPMENT == Config::getParam('env'));
$response
->addHeader('Cache-Control', 'public, max-age='.$time)
- ->addHeader('Expires', date('D, d M Y H:i:s', time() + $time).' GMT') // 45 days cache
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
->addHeader('X-UA-Compatible', 'IE=Edge'); // Deny IE browsers from going into quirks mode
$route = $utopia->match($request);
diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php
index 480c4ff9ba..ff59b03072 100644
--- a/app/controllers/web/console.php
+++ b/app/controllers/web/console.php
@@ -14,6 +14,7 @@ use Appwrite\Storage\Storage;
$utopia->init(function () use ($layout) {
$layout
+ ->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
});
@@ -277,14 +278,26 @@ $utopia->get('/console/users')
->setParam('body', $page);
});
-$utopia->get('/console/users/view')
+$utopia->get('/console/users/user')
->desc('Platform console project user')
->label('permission', 'public')
->label('scope', 'console')
->action(function () use ($layout) {
- $page = new View(__DIR__.'/../../views/console/users/view.phtml');
+ $page = new View(__DIR__.'/../../views/console/users/user.phtml');
$layout
- ->setParam('title', APP_NAME.' - View User')
+ ->setParam('title', APP_NAME.' - User')
+ ->setParam('body', $page);
+ });
+
+$utopia->get('/console/users/teams/team')
+ ->desc('Platform console project team')
+ ->label('permission', 'public')
+ ->label('scope', 'console')
+ ->action(function () use ($layout) {
+ $page = new View(__DIR__.'/../../views/console/users/team.phtml');
+
+ $layout
+ ->setParam('title', APP_NAME.' - Team')
->setParam('body', $page);
});
diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php
index f3e4fd72c8..cfec346b91 100644
--- a/app/controllers/web/home.php
+++ b/app/controllers/web/home.php
@@ -168,18 +168,18 @@ $utopia->get('/open-api-2.json')
function ($platform, $extensions, $tests) use ($response, $request, $utopia, $services) {
function fromCamelCase($input)
{
- preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
+ \preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
$ret = $matches[0];
foreach ($ret as &$match) {
- $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
+ $match = $match == \strtoupper($match) ? \strtolower($match) : \lcfirst($match);
}
- return implode('_', $ret);
+ return \implode('_', $ret);
}
function fromCamelCaseToDash($input)
{
- return str_replace([' ', '_'], '-', strtolower(preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $input)));
+ return \str_replace([' ', '_'], '-', \strtolower(\preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $input)));
}
foreach ($services as $service) { /* @noinspection PhpIncludeInspection */
@@ -196,7 +196,7 @@ $utopia->get('/open-api-2.json')
}
/** @noinspection PhpIncludeInspection */
- include_once realpath(__DIR__.'/../../'.$service['controller']);
+ include_once \realpath(__DIR__.'/../../'.$service['controller']);
}
$security = [
@@ -295,7 +295,7 @@ $utopia->get('/open-api-2.json')
'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE',
],
],
- 'host' => parse_url($request->getServer('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST),
+ 'host' => \parse_url($request->getServer('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST),
'basePath' => '/v1',
'schemes' => ['https'],
'consumes' => ['application/json', 'multipart/form-data'],
@@ -374,11 +374,11 @@ $utopia->get('/open-api-2.json')
continue;
}
- if($platform !== APP_PLATFORM_CONSOLE && !in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
+ if($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
continue;
}
- $url = str_replace('/v1', '', $route->getURL());
+ $url = \str_replace('/v1', '', $route->getURL());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
$consumes = ['application/json'];
@@ -387,14 +387,14 @@ $utopia->get('/open-api-2.json')
continue;
}
- $desc = (!empty($route->getLabel('sdk.description', ''))) ? realpath('../'.$route->getLabel('sdk.description', '')) : null;
+ $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath('../'.$route->getLabel('sdk.description', '')) : null;
$temp = [
'summary' => $route->getDesc(),
- 'operationId' => $route->getLabel('sdk.method', uniqid()),
+ 'operationId' => $route->getLabel('sdk.method', \uniqid()),
'consumes' => [],
'tags' => [$route->getLabel('sdk.namespace', 'default')],
- 'description' => ($desc) ? file_get_contents($desc) : '',
+ 'description' => ($desc) ? \file_get_contents($desc) : '',
// 'responses' => [
// 200 => [
@@ -417,6 +417,7 @@ $utopia->get('/open-api-2.json')
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
+ 'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $platformList,
];
@@ -439,7 +440,7 @@ $utopia->get('/open-api-2.json')
];
foreach ($route->getParams() as $name => $param) {
- $validator = (is_callable($param['validator'])) ? $param['validator']() : $param['validator']; /* @var $validator \Utopia\Validator */
+ $validator = (\is_callable($param['validator'])) ? $param['validator']() : $param['validator']; /* @var $validator \Utopia\Validator */
$node = [
'name' => $name,
@@ -447,14 +448,14 @@ $utopia->get('/open-api-2.json')
'required' => !$param['optional'],
];
- switch ((!empty($validator)) ? get_class($validator) : '') {
+ switch ((!empty($validator)) ? \get_class($validator) : '') {
case 'Utopia\Validator\Text':
$node['type'] = 'string';
- $node['x-example'] = '['.strtoupper(fromCamelCase($node['name'])).']';
+ $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']';
break;
case 'Appwrite\Database\Validator\UID':
$node['type'] = 'string';
- $node['x-example'] = '['.strtoupper(fromCamelCase($node['name'])).']';
+ $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']';
break;
case 'Utopia\Validator\Email':
$node['type'] = 'string';
@@ -474,7 +475,7 @@ $utopia->get('/open-api-2.json')
$node['x-example'] = '{}';
//$node['format'] = 'json';
break;
- case 'Appwrite\Storage\Validators\File':
+ case 'Appwrite\Storage\Validator\File':
$consumes = ['multipart/form-data'];
$node['type'] = 'file';
break;
@@ -516,11 +517,11 @@ $utopia->get('/open-api-2.json')
break;
}
- if ($param['optional'] && !is_null($param['default'])) { // Param has default value
+ if ($param['optional'] && !\is_null($param['default'])) { // Param has default value
$node['default'] = $param['default'];
}
- if (false !== strpos($url, ':'.$name)) { // Param is in URL path
+ if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($key == 'GET') { // Param is in query
@@ -536,12 +537,12 @@ $utopia->get('/open-api-2.json')
}
}
- $url = str_replace(':'.$name, '{'.$name.'}', $url);
+ $url = \str_replace(':'.$name, '{'.$name.'}', $url);
}
$temp['consumes'] = $consumes;
- $output['paths'][$url][strtolower($route->getMethod())] = $temp;
+ $output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
}
@@ -549,7 +550,7 @@ $utopia->get('/open-api-2.json')
var_dump($mock['name']);
}*/
- ksort($output['paths']);
+ \ksort($output['paths']);
$response
->json($output);
diff --git a/app/init.php b/app/init.php
index e66e48303e..69795f56cf 100644
--- a/app/init.php
+++ b/app/init.php
@@ -7,7 +7,7 @@
* Set configuration, framework resources, app constants
*
*/
-if (file_exists(__DIR__.'/../vendor/autoload.php')) {
+if (\file_exists(__DIR__.'/../vendor/autoload.php')) {
require_once __DIR__.'/../vendor/autoload.php';
}
@@ -50,6 +50,9 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
$register = new Registry();
$request = new Request();
$response = new Response();
+$utopia = new App('Asia/Tel_Aviv');
+
+$utopia->setMode($utopia->getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
/*
* ENV vars
@@ -60,29 +63,27 @@ Config::load('platforms', __DIR__.'/../app/config/platforms.php');
Config::load('locales', __DIR__.'/../app/config/locales.php');
Config::load('collections', __DIR__.'/../app/config/collections.php');
-Config::setParam('env', $request->getServer('_APP_ENV', App::ENV_TYPE_PRODUCTION));
+Config::setParam('env', $utopia->getMode());
Config::setParam('domain', $request->getServer('HTTP_HOST', ''));
Config::setParam('domainVerification', false);
Config::setParam('version', $request->getServer('_APP_VERSION', 'UNKNOWN'));
Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https')));
-Config::setParam('port', (string) parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT));
-Config::setParam('hostname', parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST));
-
-$utopia = new App('Asia/Tel_Aviv', Config::getParam('env'));
+Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT));
+Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST));
Resque::setBackend($request->getServer('_APP_REDIS_HOST', '')
.':'.$request->getServer('_APP_REDIS_PORT', ''));
-define('COOKIE_DOMAIN',
+\define('COOKIE_DOMAIN',
(
$request->getServer('HTTP_HOST', null) === 'localhost' ||
$request->getServer('HTTP_HOST', null) === 'localhost:'.Config::getParam('port') ||
- (filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false)
+ (\filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false)
)
? null
: '.'.Config::getParam('hostname')
);
-define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE);
+\define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE);
/*
* Registry
@@ -149,8 +150,9 @@ $register->set('smtp', function () use ($request) {
$mail->Password = $password;
$mail->SMTPSecure = $request->getServer('_APP_SMTP_SECURE', false);
$mail->SMTPAutoTLS = false;
+ $mail->CharSet = 'UTF-8';
- $from = urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'));
+ $from = \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'));
$email = $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$mail->setFrom($email, $from);
@@ -164,7 +166,7 @@ $register->set('smtp', function () use ($request) {
/*
* Localization
*/
-$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', null));
+$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', ''));
Locale::$exceptions = false;
@@ -217,14 +219,14 @@ Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php');
Locale::setDefault('en');
-if (in_array($locale, Config::getParam('locales'))) {
+if (\in_array($locale, Config::getParam('locales'))) {
Locale::setDefault($locale);
}
-stream_context_set_default([ // Set global user agent and http settings
+\stream_context_set_default([ // Set global user agent and http settings
'http' => [
'method' => 'GET',
- 'user_agent' => sprintf(APP_USERAGENT,
+ 'user_agent' => \sprintf(APP_USERAGENT,
Config::getParam('version'),
$request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)),
'timeout' => 2,
@@ -241,7 +243,7 @@ $consoleDB->setNamespace('app_console'); // Should be replaced with param if we
$consoleDB->setMocks(Config::getParam('collections', []));
Authorization::disable();
-$project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', null)));
+$project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', '')));
Authorization::enable();
@@ -265,8 +267,8 @@ $response->addHeader('X-Debug-Fallback', 'false');
if(empty($session['id']) && empty($session['secret'])) {
$response->addHeader('X-Debug-Fallback', 'true');
- $fallback = $request->getHeader('X-Fallback-Cookies', null);
- $fallback = json_decode($fallback, true);
+ $fallback = $request->getHeader('X-Fallback-Cookies', '');
+ $fallback = \json_decode($fallback, true);
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
}
@@ -278,9 +280,10 @@ $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register))
$projectDB->setNamespace('app_'.$project->getId());
$projectDB->setMocks(Config::getParam('collections', []));
-$user = $projectDB->getDocument(Auth::$unique);
-
-if (APP_MODE_ADMIN === $mode) {
+if (APP_MODE_ADMIN !== $mode) {
+ $user = $projectDB->getDocument(Auth::$unique);
+}
+else {
$user = $consoleDB->getDocument(Auth::$unique);
$user
@@ -307,7 +310,7 @@ $register->get('smtp')
->setFrom(
$request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
($project->getId() === 'console')
- ? urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'))
- : sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name')
+ ? \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'))
+ : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name')
)
);
diff --git a/app/sdks/client-flutter/README.md b/app/sdks/client-flutter/README.md
index b0c261c115..9a5c8c5c1a 100644
--- a/app/sdks/client-flutter/README.md
+++ b/app/sdks/client-flutter/README.md
@@ -2,9 +2,9 @@
[](https://pub.dartlang.org/packages/appwrite)

-
+
-**This SDK is compatible with Appwrite server version 0.6.1. For older versions, please check previous releases.**
+**This SDK is compatible with Appwrite server version 0.6.2. For older versions, please check previous releases.**
Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way.
Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools.
diff --git a/app/sdks/client-flutter/docs/examples/account/create-o-auth2session.md b/app/sdks/client-flutter/docs/examples/account/create-o-auth2session.md
index c617c34d4d..0e26dd27af 100644
--- a/app/sdks/client-flutter/docs/examples/account/create-o-auth2session.md
+++ b/app/sdks/client-flutter/docs/examples/account/create-o-auth2session.md
@@ -10,7 +10,7 @@ void main() { // Init SDK
;
Future result = account.createOAuth2Session(
- provider: 'bitbucket',
+ provider: 'amazon',
);
result
diff --git a/app/sdks/client-flutter/docs/examples/avatars/get-initials.md b/app/sdks/client-flutter/docs/examples/avatars/get-initials.md
new file mode 100644
index 0000000000..b2e70788d6
--- /dev/null
+++ b/app/sdks/client-flutter/docs/examples/avatars/get-initials.md
@@ -0,0 +1,16 @@
+import 'package:appwrite/appwrite.dart';
+
+void main() { // Init SDK
+ Client client = Client();
+ Avatars avatars = Avatars(client);
+
+ client
+ .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
+ .setProject('5df5acd0d48c2') // Your project ID
+ ;
+
+ String result = avatars.getInitials(
+ );
+
+ print(result); // Resource URL string
+}
\ No newline at end of file
diff --git a/app/sdks/client-flutter/docs/examples/locale/get-languages.md b/app/sdks/client-flutter/docs/examples/locale/get-languages.md
new file mode 100644
index 0000000000..a4124d3cbd
--- /dev/null
+++ b/app/sdks/client-flutter/docs/examples/locale/get-languages.md
@@ -0,0 +1,20 @@
+import 'package:appwrite/appwrite.dart';
+
+void main() { // Init SDK
+ Client client = Client();
+ Locale locale = Locale(client);
+
+ client
+ .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
+ .setProject('5df5acd0d48c2') // Your project ID
+ ;
+
+ Future result = locale.getLanguages( );
+
+ result
+ .then((response) {
+ print(response);
+ }).catchError((error) {
+ print(error.response);
+ });
+}
\ No newline at end of file
diff --git a/app/sdks/client-flutter/lib/services/account.dart b/app/sdks/client-flutter/lib/services/account.dart
index 8cc237d104..c2083da526 100644
--- a/app/sdks/client-flutter/lib/services/account.dart
+++ b/app/sdks/client-flutter/lib/services/account.dart
@@ -33,10 +33,10 @@ class Account extends Service {
///
/// Use this endpoint to allow a new user to register a new account in your
/// project. After the user registration completes successfully, you can use
- /// the [/account/verfication](/docs/account#createVerification) route to start
- /// verifying the user email address. To allow your new user to login to his
- /// new account, you need to create a new [account
- /// session](/docs/account#createSession).
+ /// the [/account/verfication](/docs/client/account#createVerification) route
+ /// to start verifying the user email address. To allow your new user to login
+ /// to his new account, you need to create a new [account
+ /// session](/docs/client/account#createSession).
///
Future create({@required String email, @required String password, String name = ''}) {
final String path = '/account';
@@ -195,7 +195,7 @@ class Account extends Service {
/// When the user clicks the confirmation link he is redirected back to your
/// app password reset URL with the secret key and email address values
/// attached to the URL query string. Use the query string params to submit a
- /// request to the [PUT /account/recovery](/docs/account#updateRecovery)
+ /// request to the [PUT /account/recovery](/docs/client/account#updateRecovery)
/// endpoint to complete the process.
///
Future createRecovery({@required String email, @required String url}) {
@@ -218,7 +218,7 @@ class Account extends Service {
/// Use this endpoint to complete the user account password reset. Both the
/// **userId** and **secret** arguments will be passed as query parameters to
/// the redirect URL you have provided when sending your request to the [POST
- /// /account/recovery](/docs/account#createRecovery) endpoint.
+ /// /account/recovery](/docs/client/account#createRecovery) endpoint.
///
/// Please note that in order to avoid a [Redirect
/// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
@@ -305,21 +305,36 @@ class Account extends Service {
/// first. Use the success and failure arguments to provide a redirect URL's
/// back to your app when login is completed.
///
- Future createOAuth2Session({@required String provider, String success = 'https://appwrite.io/auth/oauth2/success', String failure = 'https://appwrite.io/auth/oauth2/failure'}) {
+ Future createOAuth2Session({@required String provider, String success = 'https://appwrite.io/auth/oauth2/success', String failure = 'https://appwrite.io/auth/oauth2/failure', List scopes = const []}) {
final String path = '/account/sessions/oauth2/{provider}'.replaceAll(RegExp('{provider}'), provider);
final Map params = {
'success': success,
'failure': failure,
+ 'scopes': scopes,
'project': client.config['project'],
};
+
+ final List query = [];
+
+ params.forEach((key, value) {
+ if (value is List) {
+ for (var item in value) {
+ query.add(Uri.encodeComponent(key + '[]') + '=' + Uri.encodeComponent(item));
+ }
+ }
+ else {
+ query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value));
+ }
+ });
+
Uri endpoint = Uri.parse(client.endPoint);
Uri url = new Uri(scheme: endpoint.scheme,
host: endpoint.host,
port: endpoint.port,
path: endpoint.path + path,
- queryParameters:params,
+ query: query.join('&')
);
return FlutterWebAuth.authenticate(
@@ -361,7 +376,7 @@ class Account extends Service {
/// should redirect the user back for your app and allow you to complete the
/// verification process by verifying both the **userId** and **secret**
/// parameters. Learn more about how to [complete the verification
- /// process](/docs/account#updateAccountVerification).
+ /// process](/docs/client/account#updateAccountVerification).
///
/// Please note that in order to avoid a [Redirect
/// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
diff --git a/app/sdks/client-flutter/lib/services/avatars.dart b/app/sdks/client-flutter/lib/services/avatars.dart
index fc9c403d90..16c7ce59ff 100644
--- a/app/sdks/client-flutter/lib/services/avatars.dart
+++ b/app/sdks/client-flutter/lib/services/avatars.dart
@@ -145,6 +145,42 @@ class Avatars extends Service {
return location.toString();
}
+ /// Get User Initials
+ ///
+ /// Use this endpoint to show your user initials avatar icon on your website or
+ /// app. By default, this route will try to print your logged-in user name or
+ /// email initials. You can also overwrite the user name if you pass the 'name'
+ /// parameter. If no name is given and no user is logged, an empty avatar will
+ /// be returned.
+ ///
+ /// You can use the color and background params to change the avatar colors. By
+ /// default, a random theme will be selected. The random theme will persist for
+ /// the user's initials when reloading the same theme will always return for
+ /// the same initials.
+ ///
+ String getInitials({String name = '', int width = 500, int height = 500, String color = '', String background = ''}) {
+ final String path = '/avatars/initials';
+
+ final Map params = {
+ 'name': name,
+ 'width': width,
+ 'height': height,
+ 'color': color,
+ 'background': background,
+ 'project': client.config['project'],
+ };
+
+ Uri endpoint = Uri.parse(client.endPoint);
+ Uri location = new Uri(scheme: endpoint.scheme,
+ host: endpoint.host,
+ port: endpoint.port,
+ path: endpoint.path + path,
+ queryParameters:params,
+ );
+
+ return location.toString();
+ }
+
/// Get QR Code
///
/// Converts a given plain text to a QR code image. You can use the query
diff --git a/app/sdks/client-flutter/lib/services/database.dart b/app/sdks/client-flutter/lib/services/database.dart
index 1163c9dbb5..88082feef7 100644
--- a/app/sdks/client-flutter/lib/services/database.dart
+++ b/app/sdks/client-flutter/lib/services/database.dart
@@ -41,7 +41,10 @@ class Database extends Service {
/// Create Document
///
- /// Create a new Document.
+ /// Create a new Document. Before using this route, you should create a new
+ /// collection resource using either a [server
+ /// integration](/docs/server/database?sdk=nodejs#createCollection) API or
+ /// directly from your database console.
///
Future createDocument({@required String collectionId, @required dynamic data, @required List read, @required List write, String parentDocument = '', String parentProperty = '', String parentPropertyType = 'assign'}) {
final String path = '/database/collections/{collectionId}/documents'.replaceAll(RegExp('{collectionId}'), collectionId);
diff --git a/app/sdks/client-flutter/lib/services/locale.dart b/app/sdks/client-flutter/lib/services/locale.dart
index 91ea1567ef..095af4792c 100644
--- a/app/sdks/client-flutter/lib/services/locale.dart
+++ b/app/sdks/client-flutter/lib/services/locale.dart
@@ -106,9 +106,9 @@ class Locale extends Service {
/// List Currencies
///
- /// List of all currencies, including currency symol, name, plural, and decimal
- /// digits for all major and minor currencies. You can use the locale header to
- /// get the data in a supported language.
+ /// List of all currencies, including currency symbol, name, plural, and
+ /// decimal digits for all major and minor currencies. You can use the locale
+ /// header to get the data in a supported language.
///
Future getCurrencies() {
final String path = '/locale/currencies';
@@ -120,6 +120,24 @@ class Locale extends Service {
'content-type': 'application/json',
};
+ return client.call(HttpMethod.get, path: path, params: params, headers: headers);
+ }
+
+ /// List Languages
+ ///
+ /// List of all languages classified by ISO 639-1 including 2-letter code, name
+ /// in English, and name in the respective language.
+ ///
+ Future getLanguages() {
+ final String path = '/locale/languages';
+
+ final Map params = {
+ };
+
+ final Map headers = {
+ 'content-type': 'application/json',
+ };
+
return client.call(HttpMethod.get, path: path, params: params, headers: headers);
}
}
\ No newline at end of file
diff --git a/app/sdks/client-flutter/lib/services/teams.dart b/app/sdks/client-flutter/lib/services/teams.dart
index 56e0731687..b9205da455 100644
--- a/app/sdks/client-flutter/lib/services/teams.dart
+++ b/app/sdks/client-flutter/lib/services/teams.dart
@@ -115,10 +115,14 @@ class Teams extends Service {
/// Get team members by the team unique ID. All team members have read access
/// for this list of resources.
///
- Future getMemberships({@required String teamId}) {
+ Future getMemberships({@required String teamId, String search = '', int limit = 25, int offset = 0, OrderType orderType = OrderType.asc}) {
final String path = '/teams/{teamId}/memberships'.replaceAll(RegExp('{teamId}'), teamId);
final Map params = {
+ 'search': search,
+ 'limit': limit,
+ 'offset': offset,
+ 'orderType': orderType.name(),
};
final Map headers = {
@@ -136,8 +140,8 @@ class Teams extends Service {
///
/// Use the 'URL' parameter to redirect the user from the invitation email back
/// to your app. When the user is redirected, use the [Update Team Membership
- /// Status](/docs/teams#updateMembershipStatus) endpoint to allow the user to
- /// accept the invitation to the team.
+ /// Status](/docs/client/teams#updateMembershipStatus) endpoint to allow the
+ /// user to accept the invitation to the team.
///
/// Please note that in order to avoid a [Redirect
/// Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
diff --git a/app/sdks/client-web/README.md b/app/sdks/client-web/README.md
index e59480b4a9..093783ab41 100644
--- a/app/sdks/client-web/README.md
+++ b/app/sdks/client-web/README.md
@@ -1,7 +1,7 @@
# Appwrite Web SDK

-
+
Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way.
Use the Web SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools.
diff --git a/app/sdks/client-web/docs/examples/account/create-o-auth2session.md b/app/sdks/client-web/docs/examples/account/create-o-auth2session.md
index 5cc825a273..25b02e632e 100644
--- a/app/sdks/client-web/docs/examples/account/create-o-auth2session.md
+++ b/app/sdks/client-web/docs/examples/account/create-o-auth2session.md
@@ -5,10 +5,6 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
-let promise = sdk.account.createOAuth2Session('bitbucket');
+// Go to OAuth provider login page
+sdk.account.createOAuth2Session('amazon');
-promise.then(function (response) {
- console.log(response); // Success
-}, function (error) {
- console.log(error); // Failure
-});
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-browser.md b/app/sdks/client-web/docs/examples/avatars/get-browser.md
index 86d0df4a23..0855209f58 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-browser.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-browser.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getBrowser('aa');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-credit-card.md b/app/sdks/client-web/docs/examples/avatars/get-credit-card.md
index 83a1381adf..1aa595bd65 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-credit-card.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-credit-card.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getCreditCard('amex');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-favicon.md b/app/sdks/client-web/docs/examples/avatars/get-favicon.md
index c0ff93307a..a945e50bb2 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-favicon.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-favicon.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getFavicon('https://example.com');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-flag.md b/app/sdks/client-web/docs/examples/avatars/get-flag.md
index 762aeb348e..222b388afc 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-flag.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-flag.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getFlag('af');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-image.md b/app/sdks/client-web/docs/examples/avatars/get-image.md
index 58f0eb8077..3f15981c71 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-image.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-image.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getImage('https://example.com');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-initials.md b/app/sdks/client-web/docs/examples/avatars/get-initials.md
new file mode 100644
index 0000000000..ef04988e2d
--- /dev/null
+++ b/app/sdks/client-web/docs/examples/avatars/get-initials.md
@@ -0,0 +1,10 @@
+let sdk = new Appwrite();
+
+sdk
+ .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
+ .setProject('5df5acd0d48c2') // Your project ID
+;
+
+let result = sdk.avatars.getInitials();
+
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/avatars/get-q-r.md b/app/sdks/client-web/docs/examples/avatars/get-q-r.md
index fb17032949..9d9ee3fea2 100644
--- a/app/sdks/client-web/docs/examples/avatars/get-q-r.md
+++ b/app/sdks/client-web/docs/examples/avatars/get-q-r.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.avatars.getQR('[TEXT]');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/locale/get-languages.md b/app/sdks/client-web/docs/examples/locale/get-languages.md
new file mode 100644
index 0000000000..04e3063ee7
--- /dev/null
+++ b/app/sdks/client-web/docs/examples/locale/get-languages.md
@@ -0,0 +1,14 @@
+let sdk = new Appwrite();
+
+sdk
+ .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
+ .setProject('5df5acd0d48c2') // Your project ID
+;
+
+let promise = sdk.locale.getLanguages();
+
+promise.then(function (response) {
+ console.log(response); // Success
+}, function (error) {
+ console.log(error); // Failure
+});
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/storage/get-file-download.md b/app/sdks/client-web/docs/examples/storage/get-file-download.md
index ae3a643529..1323b8b8df 100644
--- a/app/sdks/client-web/docs/examples/storage/get-file-download.md
+++ b/app/sdks/client-web/docs/examples/storage/get-file-download.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.storage.getFileDownload('[FILE_ID]');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/storage/get-file-preview.md b/app/sdks/client-web/docs/examples/storage/get-file-preview.md
index 30461e4d9a..5ede9fdb93 100644
--- a/app/sdks/client-web/docs/examples/storage/get-file-preview.md
+++ b/app/sdks/client-web/docs/examples/storage/get-file-preview.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.storage.getFilePreview('[FILE_ID]');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/docs/examples/storage/get-file-view.md b/app/sdks/client-web/docs/examples/storage/get-file-view.md
index 341dd502de..53877ae352 100644
--- a/app/sdks/client-web/docs/examples/storage/get-file-view.md
+++ b/app/sdks/client-web/docs/examples/storage/get-file-view.md
@@ -7,4 +7,4 @@ sdk
let result = sdk.storage.getFileView('[FILE_ID]');
-console.log(result); // Resource URL
+console.log(result); // Resource URL
\ No newline at end of file
diff --git a/app/sdks/client-web/src/sdk.js b/app/sdks/client-web/src/sdk.js
index 0d7a7deaab..5c98ea89b3 100644
--- a/app/sdks/client-web/src/sdk.js
+++ b/app/sdks/client-web/src/sdk.js
@@ -190,28 +190,27 @@
}
request.onload = function () {
+ let data = request.response;
+ let contentType = this.getResponseHeader('content-type') || '';
+ contentType = contentType.substring(0, contentType.indexOf(';'));
+
+ switch (contentType) {
+ case 'application/json':
+ data = JSON.parse(data);
+ break;
+ }
+
+ let cookieFallback = this.getResponseHeader('X-Fallback-Cookies') || '';
+
+ if(window.localStorage && cookieFallback) {
+ window.console.warn('Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
+ window.localStorage.setItem('cookieFallback', cookieFallback);
+ }
+
if (4 === request.readyState && 399 >= request.status) {
- let data = request.response;
- let contentType = this.getResponseHeader('content-type') || '';
- contentType = contentType.substring(0, contentType.indexOf(';'));
-
- switch (contentType) {
- case 'application/json':
- data = JSON.parse(data);
- break;
- }
-
- let cookieFallback = this.getResponseHeader('X-Fallback-Cookies') || '';
-
- if(window.localStorage && cookieFallback) {
- window.console.warn('Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
- window.localStorage.setItem('cookieFallback', cookieFallback);
- }
-
resolve(data);
-
} else {
- reject(new Error(request.statusText));
+ reject(data);
}
};
@@ -276,10 +275,10 @@
*
* Use this endpoint to allow a new user to register a new account in your
* project. After the user registration completes successfully, you can use
- * the [/account/verfication](/docs/account#createVerification) route to start
- * verifying the user email address. To allow your new user to login to his
- * new account, you need to create a new [account
- * session](/docs/account#createSession).
+ * the [/account/verfication](/docs/client/account#createVerification) route
+ * to start verifying the user email address. To allow your new user to login
+ * to his new account, you need to create a new [account
+ * session](/docs/client/account#createSession).
*
* @param {string} email
* @param {string} password
@@ -522,7 +521,7 @@
* When the user clicks the confirmation link he is redirected back to your
* app password reset URL with the secret key and email address values
* attached to the URL query string. Use the query string params to submit a
- * request to the [PUT /account/recovery](/docs/account#updateRecovery)
+ * request to the [PUT /account/recovery](/docs/client/account#updateRecovery)
* endpoint to complete the process.
*
* @param {string} email
@@ -563,7 +562,7 @@
* Use this endpoint to complete the user account password reset. Both the
* **userId** and **secret** arguments will be passed as query parameters to
* the redirect URL you have provided when sending your request to the [POST
- * /account/recovery](/docs/account#createRecovery) endpoint.
+ * /account/recovery](/docs/client/account#createRecovery) endpoint.
*
* Please note that in order to avoid a [Redirect
* Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
@@ -709,10 +708,11 @@
* @param {string} provider
* @param {string} success
* @param {string} failure
+ * @param {string[]} scopes
* @throws {Error}
* @return {Promise}
*/
- createOAuth2Session: function(provider, success = 'https://appwrite.io/auth/oauth2/success', failure = 'https://appwrite.io/auth/oauth2/failure') {
+ createOAuth2Session: function(provider, success = 'https://appwrite.io/auth/oauth2/success', failure = 'https://appwrite.io/auth/oauth2/failure', scopes = []) {
if(provider === undefined) {
throw new Error('Missing required parameter: "provider"');
}
@@ -729,9 +729,28 @@
payload['failure'] = failure;
}
+ if(scopes) {
+ payload['scopes'] = scopes;
+ }
+
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
window.location = config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -772,7 +791,7 @@
* should redirect the user back for your app and allow you to complete the
* verification process by verifying both the **userId** and **secret**
* parameters. Learn more about how to [complete the verification
- * process](/docs/account#updateAccountVerification).
+ * process](/docs/client/account#updateAccountVerification).
*
* Please note that in order to avoid a [Redirect
* Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
@@ -883,7 +902,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -926,7 +960,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -956,7 +1005,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -998,7 +1062,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -1040,7 +1119,91 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
+
+ return config.endpoint + path + ((query) ? '?' + query : '');
+ },
+
+ /**
+ * Get User Initials
+ *
+ * Use this endpoint to show your user initials avatar icon on your website or
+ * app. By default, this route will try to print your logged-in user name or
+ * email initials. You can also overwrite the user name if you pass the 'name'
+ * parameter. If no name is given and no user is logged, an empty avatar will
+ * be returned.
+ *
+ * You can use the color and background params to change the avatar colors. By
+ * default, a random theme will be selected. The random theme will persist for
+ * the user's initials when reloading the same theme will always return for
+ * the same initials.
+ *
+ * @param {string} name
+ * @param {number} width
+ * @param {number} height
+ * @param {string} color
+ * @param {string} background
+ * @throws {Error}
+ * @return {string}
+ */
+ getInitials: function(name = '', width = 500, height = 500, color = '', background = '') {
+ let path = '/avatars/initials';
+
+ let payload = {};
+
+ if(name) {
+ payload['name'] = name;
+ }
+
+ if(width) {
+ payload['width'] = width;
+ }
+
+ if(height) {
+ payload['height'] = height;
+ }
+
+ if(color) {
+ payload['color'] = color;
+ }
+
+ if(background) {
+ payload['background'] = background;
+ }
+
+ payload['project'] = config.project;
+
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -1085,7 +1248,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
}
@@ -1168,7 +1346,10 @@
/**
* Create Document
*
- * Create a new Document.
+ * Create a new Document. Before using this route, you should create a new
+ * collection resource using either a [server
+ * integration](/docs/server/database?sdk=nodejs#createCollection) API or
+ * directly from your database console.
*
* @param {string} collectionId
* @param {object} data
@@ -1457,9 +1638,9 @@
/**
* List Currencies
*
- * List of all currencies, including currency symol, name, plural, and decimal
- * digits for all major and minor currencies. You can use the locale header to
- * get the data in a supported language.
+ * List of all currencies, including currency symbol, name, plural, and
+ * decimal digits for all major and minor currencies. You can use the locale
+ * header to get the data in a supported language.
*
* @throws {Error}
* @return {Promise}
@@ -1469,6 +1650,26 @@
let payload = {};
+ return http
+ .get(path, {
+ 'content-type': 'application/json',
+ }, payload);
+ },
+
+ /**
+ * List Languages
+ *
+ * List of all languages classified by ISO 639-1 including 2-letter code, name
+ * in English, and name in the respective language.
+ *
+ * @throws {Error}
+ * @return {Promise}
+ */
+ getLanguages: function() {
+ let path = '/locale/languages';
+
+ let payload = {};
+
return http
.get(path, {
'content-type': 'application/json',
@@ -1682,7 +1883,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -1735,7 +1951,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
},
@@ -1766,7 +1997,22 @@
payload['project'] = config.project;
- let query = Object.keys(payload).map(key => key + '=' + encodeURIComponent(payload[key])).join('&');
+
+ let query = [];
+
+ for (let p in payload) {
+ if(Array.isArray(payload[p])) {
+ for (let index = 0; index < payload[p].length; index++) {
+ let param = payload[p][index];
+ query.push(encodeURIComponent(p + '[]') + "=" + encodeURIComponent(param));
+ }
+ }
+ else {
+ query.push(encodeURIComponent(p) + "=" + encodeURIComponent(payload[p]));
+ }
+ }
+
+ query = query.join("&");
return config.endpoint + path + ((query) ? '?' + query : '');
}
@@ -1942,10 +2188,14 @@
* for this list of resources.
*
* @param {string} teamId
+ * @param {string} search
+ * @param {number} limit
+ * @param {number} offset
+ * @param {string} orderType
* @throws {Error}
* @return {Promise}
*/
- getMemberships: function(teamId) {
+ getMemberships: function(teamId, search = '', limit = 25, offset = 0, orderType = 'ASC') {
if(teamId === undefined) {
throw new Error('Missing required parameter: "teamId"');
}
@@ -1954,6 +2204,22 @@
let payload = {};
+ if(search) {
+ payload['search'] = search;
+ }
+
+ if(limit) {
+ payload['limit'] = limit;
+ }
+
+ if(offset) {
+ payload['offset'] = offset;
+ }
+
+ if(orderType) {
+ payload['orderType'] = orderType;
+ }
+
return http
.get(path, {
'content-type': 'application/json',
@@ -1969,8 +2235,8 @@
*
* Use the 'URL' parameter to redirect the user from the invitation email back
* to your app. When the user is redirected, use the [Update Team Membership
- * Status](/docs/teams#updateMembershipStatus) endpoint to allow the user to
- * accept the invitation to the team.
+ * Status](/docs/client/teams#updateMembershipStatus) endpoint to allow the
+ * user to accept the invitation to the team.
*
* Please note that in order to avoid a [Redirect
* Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
diff --git a/app/sdks/client-web/src/sdk.min.js b/app/sdks/client-web/src/sdk.min.js
index 113a3402b8..7b470dc380 100644
--- a/app/sdks/client-web/src/sdk.min.js
+++ b/app/sdks/client-web/src/sdk.min.js
@@ -9,9 +9,9 @@ if(method==='GET'){for(let param in params){if(param.hasOwnProperty(key)){path=a
switch(headers['content-type']){case 'application/json':params=JSON.stringify(params);break;case 'multipart/form-data':let formData=new FormData();Object.keys(params).forEach(function(key){let param=params[key];formData.append(key+(Array.isArray(param)?'[]':''),param)});params=formData;break}
return new Promise(function(resolve,reject){let request=new XMLHttpRequest(),key;request.withCredentials=!0;request.open(method,path,!0);for(key in headers){if(headers.hasOwnProperty(key)){if(key==='content-type'&&headers[key]==='multipart/form-data'){continue}
request.setRequestHeader(key,headers[key])}}
-request.onload=function(){if(4===request.readyState&&399>=request.status){let data=request.response;let contentType=this.getResponseHeader('content-type')||'';contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case 'application/json':data=JSON.parse(data);break}
+request.onload=function(){let data=request.response;let contentType=this.getResponseHeader('content-type')||'';contentType=contentType.substring(0,contentType.indexOf(';'));switch(contentType){case 'application/json':data=JSON.parse(data);break}
let cookieFallback=this.getResponseHeader('X-Fallback-Cookies')||'';if(window.localStorage&&cookieFallback){window.console.warn('Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');window.localStorage.setItem('cookieFallback',cookieFallback)}
-resolve(data)}else{reject(new Error(request.statusText))}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,!1)}
+if(4===request.readyState&&399>=request.status){resolve(data)}else{reject(data)}};if(progress){request.addEventListener('progress',progress);request.upload.addEventListener('progress',progress,!1)}
request.onerror=function(){reject(new Error("Network Error"))};request.send(params)})};return{'get':function(path,headers={},params={}){return call('GET',path+((Object.keys(params).length>0)?'?'+buildQuery(params):''),headers,{})},'post':function(path,headers={},params={},progress=null){return call('POST',path,headers,params,progress)},'put':function(path,headers={},params={},progress=null){return call('PUT',path,headers,params,progress)},'patch':function(path,headers={},params={},progress=null){return call('PATCH',path,headers,params,progress)},'delete':function(path,headers={},params={},progress=null){return call('DELETE',path,headers,params,progress)},'addGlobalParam':addGlobalParam,'addGlobalHeader':addGlobalHeader}}(window.document);let account={get:function(){let path='/account';let payload={};return http.get(path,{'content-type':'application/json',},payload)},create:function(email,password,name=''){if(email===undefined){throw new Error('Missing required parameter: "email"')}
if(password===undefined){throw new Error('Missing required parameter: "password"')}
let path='/account';let payload={};if(email){payload.email=email}
@@ -45,10 +45,12 @@ return http.put(path,{'content-type':'application/json',},payload)},getSessions:
if(password===undefined){throw new Error('Missing required parameter: "password"')}
let path='/account/sessions';let payload={};if(email){payload.email=email}
if(password){payload.password=password}
-return http.post(path,{'content-type':'application/json',},payload)},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload)},createOAuth2Session:function(provider,success='https://appwrite.io/auth/oauth2/success',failure='https://appwrite.io/auth/oauth2/failure'){if(provider===undefined){throw new Error('Missing required parameter: "provider"')}
+return http.post(path,{'content-type':'application/json',},payload)},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload)},createOAuth2Session:function(provider,success='https://appwrite.io/auth/oauth2/success',failure='https://appwrite.io/auth/oauth2/failure',scopes=[]){if(provider===undefined){throw new Error('Missing required parameter: "provider"')}
let path='/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload.success=success}
if(failure){payload.failure=failure}
-payload.project=config.project;let query=Object.keys(payload).map(key=>key+'='+encodeURIComponent(payload[key])).join('&');window.location=config.endpoint+path+((query)?'?'+query:'')},deleteSession:function(sessionId){if(sessionId===undefined){throw new Error('Missing required parameter: "sessionId"')}
+if(scopes){payload.scopes=scopes}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getCreditCard:function(code,width=100,height=100,quality=100){if(code===undefined){throw new Error('Missing required parameter: "code"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getFavicon:function(url){if(url===undefined){throw new Error('Missing required parameter: "url"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getFlag:function(code,width=100,height=100,quality=100){if(code===undefined){throw new Error('Missing required parameter: "code"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getImage:function(url,width=400,height=400){if(url===undefined){throw new Error('Missing required parameter: "url"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getQR:function(text,size=400,margin=1,download=0){if(text===undefined){throw new Error('Missing required parameter: "text"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')}};let database={listDocuments:function(collectionId,filters=[],offset=0,limit=50,orderField='$id',orderType='ASC',orderCast='string',search='',first=0,last=0){if(collectionId===undefined){throw new Error('Missing required parameter: "collectionId"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getFilePreview:function(fileId,width=0,height=0,quality=100,background='',output=''){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"')}
+let path='/storage/files/{fileId}/download'.replace(new RegExp('{fileId}','g'),fileId);let payload={};payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},getFileView:function(fileId,as=''){if(fileId===undefined){throw new Error('Missing required parameter: "fileId"')}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;indexkey+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')}};let teams={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/teams';let payload={};if(search){payload.search=search}
+payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index;
+ createOAuth2Session(provider: string, success: string, failure: string, scopes: string[]): Promise