modif comtabilite
@@ -0,0 +1,29 @@
|
||||
email: anissa.bensalah@gmail.com
|
||||
fullname: 'Anissa bensalah'
|
||||
title: Modérator
|
||||
state: enabled
|
||||
access:
|
||||
admin:
|
||||
super: 'false'
|
||||
login: 'true'
|
||||
cache: 'false'
|
||||
configuration: 'false'
|
||||
configuration_system: 'false'
|
||||
configuration_site: 'false'
|
||||
configuration_media: 'false'
|
||||
configuration_info: 'false'
|
||||
settings: 'false'
|
||||
pages: 'true'
|
||||
maintenance: 'false'
|
||||
statistics: 'true'
|
||||
plugins: 'false'
|
||||
themes: 'false'
|
||||
users: 'false'
|
||||
site:
|
||||
login: 'true'
|
||||
hashed_password: $2y$10$oKsKkddquAgbF6hQnYSP1.8MGJQOewwBoYWrNpTKnpZTXGZjcSNgu
|
||||
groups:
|
||||
- moderator
|
||||
language: en
|
||||
twofa_enabled: false
|
||||
twofa_secret: FILKLI3TRO3LDFLG4DUATUQVJPZFZ4ID
|
||||
@@ -0,0 +1,11 @@
|
||||
moderator:
|
||||
groupname: moderator
|
||||
readableName: Modérator
|
||||
description: 'The group of Modérator'
|
||||
icon: child
|
||||
access:
|
||||
admin:
|
||||
login: true
|
||||
cache: false
|
||||
site:
|
||||
login: true
|
||||
@@ -1,19 +1,19 @@
|
||||
enabled: true
|
||||
built_in_css: true
|
||||
add_editor_button: true
|
||||
add_editor_button: false
|
||||
player_parameters:
|
||||
autoplay: 0
|
||||
cc_load_policy: 0
|
||||
cc_load_policy: 1
|
||||
color: red
|
||||
controls: 1
|
||||
disablekb: 0
|
||||
enablejsapi: 0
|
||||
fs: 1
|
||||
iv_load_policy: 1
|
||||
iv_load_policy: 3
|
||||
loop: 0
|
||||
modestbranding: 0
|
||||
playsinline: 0
|
||||
rel: 1
|
||||
rel: 0
|
||||
showinfo: 0
|
||||
vq: default
|
||||
privacy_enhanced_mode: false
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
absolute_urls: false
|
||||
timezone: ''
|
||||
default_locale: null
|
||||
param_sep: ':'
|
||||
wrapped_site: false
|
||||
reverse_proxy_setup: false
|
||||
force_ssl: false
|
||||
force_lowercase_urls: true
|
||||
custom_base_url: ''
|
||||
username_regex: '^[a-z0-9_-]{3,16}$'
|
||||
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
intl_enabled: true
|
||||
@@ -32,7 +29,6 @@ pages:
|
||||
list:
|
||||
count: 20
|
||||
dateformat:
|
||||
default: null
|
||||
short: 'jS M Y'
|
||||
long: 'F jS \a\t g:ia'
|
||||
publish_dates: true
|
||||
@@ -60,9 +56,7 @@ pages:
|
||||
- json
|
||||
- rss
|
||||
- atom
|
||||
append_url_extension: ''
|
||||
expires: 604800
|
||||
cache_control: null
|
||||
last_modified: false
|
||||
etag: false
|
||||
vary_accept_encoding: false
|
||||
@@ -92,8 +86,6 @@ cache:
|
||||
lifetime: 604800
|
||||
gzip: false
|
||||
allow_webserver_gzip: false
|
||||
redis:
|
||||
socket: false
|
||||
twig:
|
||||
cache: false
|
||||
debug: true
|
||||
@@ -117,7 +109,7 @@ assets:
|
||||
collections:
|
||||
jquery: 'system://assets/jquery/jquery-2.x.min.js'
|
||||
errors:
|
||||
display: '1'
|
||||
display: 1
|
||||
log: true
|
||||
debugger:
|
||||
enabled: false
|
||||
@@ -126,14 +118,12 @@ debugger:
|
||||
twig: true
|
||||
images:
|
||||
default_image_quality: 85
|
||||
cache_all: false
|
||||
cache_all: true
|
||||
cache_perms: '0755'
|
||||
debug: false
|
||||
auto_fix_orientation: false
|
||||
media:
|
||||
enable_media_timestamp: false
|
||||
unsupported_inline_types: { }
|
||||
allowed_fallback_types: { }
|
||||
auto_metadata_exif: false
|
||||
upload_limit: 2097152
|
||||
session:
|
||||
@@ -144,10 +134,11 @@ session:
|
||||
secure: false
|
||||
httponly: true
|
||||
split: true
|
||||
path: null
|
||||
gpm:
|
||||
releases: stable
|
||||
proxy_url: null
|
||||
method: auto
|
||||
verify_peer: true
|
||||
official_gpm_only: true
|
||||
strict_mode:
|
||||
yaml_compat: true
|
||||
twig_compat: true
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: fullvideo
|
||||
image_align: left
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=OrspkA-bp3Q)
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: fullvideo
|
||||
image_align: left
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=OrspkA-bp3Q)
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: fullvideo
|
||||
image_align: left
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=OrspkA-bp3Q)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: fullvideo
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=atTcasPy3JY)
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- sovaj
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=Fbz00uIc_pU)
|
||||
@@ -6,4 +6,4 @@ taxonomy:
|
||||
- sovaj
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=atTcasPy3JY)
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=Fbz00uIc_pU)
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- sovaj
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=Fbz00uIc_pU)
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: matriz
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- matriz
|
||||
---
|
||||
|
||||
[plugin:youtube](https://www.youtube.com/watch?v=atTcasPy3JY)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: VIDEOS
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: VÍDEOS
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 9.3 MiB |
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 5.5 MiB |
|
After Width: | Height: | Size: 10 MiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 5.4 MiB |
@@ -4,4 +4,4 @@ media_order: 'IMG_2636.JPG,IMG_2704.JPG,IMG_2706.JPG,IMG_2707.JPG,IMG_2730.JPG'
|
||||
image_align: left
|
||||
---
|
||||
|
||||

|
||||

|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Sovaj
|
||||
media_order: 'ANISSA-4.jpg,ANISSA-7.jpg,JuSana4.jpg,Anissa marge et Logo-6.jpg,Sans titre-2.jpg,ANISSA-10.jpg,IMG_9373-10.jpg'
|
||||
image_align: left
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Sovaj
|
||||
media_order: 'IMG_2636.JPG,IMG_2704.JPG,IMG_2706.JPG,IMG_2707.JPG,IMG_2730.JPG'
|
||||
image_align: left
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Gallery
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: FOTOS
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- sovaj
|
||||
---
|
||||
|
||||
French, Algerian and Brazilian Artist, Anissá Bensalah sings on stage since her 15 years old : Sesc, Itamaratí, Palacio de Cristal, Candelária (Rio de Janeiro, Brazil), Teatro Nacional (Brasilia, Brazil), Salle Ibn Zeydoun (Algiers), Salle Gaveau, New Morning, Institut du Monde Arabe, festival Solidays, Théâtre des Gémeaux, Centre Culturel Goulbenkian, Maroquinerie, Sunside-Sunset, Petit Bain, Studio de l'Ermitage, Salle Gustave Eiffel (Paris, France)... Dock 40 (Lyon, France), Théâtre de la Mer (Sainte Maxime, France), Festival Jazz au Château (Cagnes sur Mer, France), Festival La Rue des Artistes (Saint Chamont)...
|
||||
|
||||
Born in Haïti, Anissá Bensalah moved at the age of five, with her parents, to Dakar (Senegal) where she immersed herself in the local culture. At the age of 11, she moved to Beirut (Lebanon) where she discovered the damage of war.
|
||||
In 1998, she moved to Paris and studied musicology at the Paris VIII University. She also pursued ten years of classical vocal technic at the National School of Music with Odile Pietti, Florence Katz and Mercedes Proteau, and she studied jazz and brazilian music with Laurent Coq (french pianist jazz), Mônica Passos (brazilian singer) in Paris, Diana Goulart and the famous Amelia Rabello (brazilian singers) in Rio de Janeiro.
|
||||
|
||||
Anissá Bensalah has worked and collaborated with some important figures of brazilian music : The pianist Jõao Donato who gave her a new composition _Equanto a gente namora_, the guitarist Nelson Faria, the pianist and composer Rodrigo Zaidan, the bassist Josias Pedrosa and the drumer Lucio Vieira with whom she recorded a 4 tracks extended play, _Eu sei e você sabe._
|
||||
|
||||
In october 2013, she has released her first original album _Matriz_, produced by Ovastand (France) and distributed by Musicast. This album is played by french instrumentists : Automne Lajeat (cello), Frédéric Antetomaso (guitars), Julien Matrot (trumpet), Gui Duvignau (double bass) et Nils Wekstein (drums & percussions).
|
||||
|
||||
In April 2014, she has a landmark encounter with Remy Kolpa Kopoul (aslo known as RKK), a key journalist-"connector" of the French music industry. He will give her all his trust and attention, opening many doors for her.
|
||||
|
||||
In 2015, she recorded a 4 tracks extended play, _Nejma_, with Amina Mezaache (flute), Frédéric Antetomaso (guitars), Ricardo Feijão (acoustic bass), Nils Wekstein and Jonathan Edo (drums & percussions).
|
||||
|
||||
Currently, Anissá Bensalah presents her second album _Sovaj_ (produced by Ovastand) in France and other countries. She sings on stage with Tim Campanella (drums), Philippe Monge (bass & keyboards) and Frédéric Antetomaso (guitars).
|
||||
@@ -12,12 +12,12 @@ Née en Haïti, Anissá Bensalah part à 5 ans, avec ses parents, vivre à Dakar
|
||||
|
||||
Ce n'est qu'à ses 14 ans qu'elle arrive à Paris. Parallèlement à un cursus de musicologie à l'Université Paris VIII, elle y suit dix ans de formation en École Nationale de Musique auprès d'Odile Pietti, Florence Katz et Mercedes Proteau (classique), ainsi que Laurent Coq, Mônica Passos, Diana Goulart et Amélia Rabello (jazz et musiques brésiliennes).
|
||||
|
||||
Elle a travaillé et collaboré avec des noms de la musique brésilienne : João Donato qui lui a confié une composition inédite Enquanto a gente namora, Nelson Faria, Rodrigo Zaidan, Josias Pedrosa et Lucio Vieira avec qui elle a enregistré un EP 4 titres auto-produit Eu sei e você sabe.
|
||||
Elle a travaillé et collaboré avec des noms de la musique brésilienne : João Donato qui lui a confié une composition inédite _Enquanto a gente namora_, Nelson Faria, Rodrigo Zaidan, Josias Pedrosa et Lucio Vieira avec qui elle a enregistré un EP 4 titres auto-produit _Eu sei e você sabe_.
|
||||
|
||||
En octobre 2013, elle sort son premier album Matriz, sous le label Ovastand et distribué par Musicast. Elle y est entourée de musiciens variées comme Automne Lajeat (violoncelle), Frédéric Antetomaso (guitare), Julien Matrot (trompette), Guillaume Duvignau (contrebasse) et Nils Wekstein (batterie-percussions).
|
||||
En octobre 2013, elle sort son premier album _Matriz_, sous le label Ovastand et distribué par Musicast. Elle y est entourée de musiciens variées comme Automne Lajeat (violoncelle), Frédéric Antetomaso (guitare), Julien Matrot (trompette), Gui Duvignau (contrebasse) et Nils Wekstein (batterie-percussions).
|
||||
|
||||
En avril 2014, elle fait une rencontre professionnelle marquante : Rémy Kolpa Kopoul (surnommé RKK), journaliste-"connexionneur" incontrounable dans le milieu musical en France. Il lui accordera sa confiance et son écoute, et lui ouvrira plusieurs portes.
|
||||
|
||||
Fin 2015, elle enregistre un EP 4 titres, intitulé Nejma, avec Amina Mezaache (flûtes), Frédéric Antetomaso (guitares), Ricardo Feijão (basse acoustique), Nils Wekstein et Jonathan Edo (batterie-percussions).
|
||||
Fin 2015, elle enregistre un EP 4 titres, intitulé _Nejma_, avec Amina Mezaache (flûtes), Frédéric Antetomaso (guitares), Ricardo Feijão (basse acoustique), Nils Wekstein et Jonathan Edo (batterie-percussions).
|
||||
|
||||
Actuellement, Anissá Bensalah présente sur scène son deuxième album Sovaj (signé chez Ovastand), en France et à l'étranger. Elle est accompagnée par Tim Campanella (batterie), Philippe Monge (synthé basse et claviers) et Frédéric Antetomaso (guitares).
|
||||
Actuellement, Anissá Bensalah présente sur scène son deuxième album _Sovaj_ (signé chez Ovastand), en France et à l'étranger. Elle est accompagnée par Tim Campanella (batterie), Philippe Monge (synthé basse et claviers) et Frédéric Antetomaso (guitares).
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- sovaj
|
||||
---
|
||||
|
||||
Artista franco-argelina e brasileira, Anissá Bensalah se apresenta no palco como cantora desde seus 15 anos : Sala Ibn Zeydoun (Argélia), SESC (RJ), Palácio de Cristal (Petrópolis), Igreja da Candelária (RJ), Teatro Municipal de Cabo Frio, Salão Nobre do Itamaratí (RJ), Hotel Gloria (RJ), Sala Villa Lobos do Teatro Nacional (Brasilia), Salle Gaveau (Paris), New Morning (Paris), festival nacional francês Solidays (Paris-Vincennes), Institut du monde Arabe (Paris), Maroquinerie (Paris), Sunside-Sunset (Paris), Petit Bain (Paris), Studio de l'Ermitage (Paris), Salle Gustave Eiffel (Paris), Théâtre de la Mer (Sainte Maxime), Festival Jazz au Château (Cagnes sur Mer), Dock 40 (Lyon), Festival La Rue des Artistes (Saint Chamont), etc.
|
||||
|
||||
Nascida no Haïti, Anissá se despede de sua terra natal aos 5 anos de idade para viver em Dakar (Senegal), onde se impregna do rítmo e da cultura locais. Aos 11 anos, muda-se para o Líbano onde descobre uma terra devastada pela guerra. Com 14 anos, se estabelece em Paris. Paralelamente aos seus diplomas de musicologia na Universidade Paris VIII, segue durante 10 anos uma formação de canto no Conservatório Nacional de Música em Paris com Odile Pietti, Florence Katz e Mercedes Proteau (formação em canto lírico) e Laurent Cocq, Mônica Passos, Diana Goulart e Amélia Rabello (formação jazz, MPB e samba).
|
||||
|
||||
Entre 2008 e 2009, encontra com João Donato que lhe confia uma de suas composiçãos inéditas com letra de Thelma de Freitas, _Enquanto a gente namora_.
|
||||
Com a colaboração de Diana Goulart (direção artística) e Nelson Faria (guitarra e arranjos), grava um EP com 4 músicas, _Eu sei e você sabe_, com a participação de Rodrigo Zaidan, Josías Pedrosa e Lucio Vieira.
|
||||
|
||||
Em outubro 2013, sai _Matriz_, o primeiro album de Anissá, lançado pela Ovastand (França) e distribuido pela Musicast. Ela é acompanhada por vários instrumentistas franceses : Automne Lajeat (cello), Frédéric Antetomaso (violao), Julien Matrot (trompete), Gui Duvignau (contrabaixo) et Nils Wekstein (bateria e percussões).
|
||||
|
||||
Em abril 2014, encountrou com o famoso jornalista francês e grande amador de música : Remy Kolpa Kopoul (conhecido como "RKK"). Conquistado pela música de Anissá, ele lhe abri varias portas no setor musical francês.
|
||||
|
||||
No final do ano 2015, grava um EP com 4 músicas, _Nejma_, junto com Amina Mezaache (flautas), Frédéric Antetomaso (violão), Ricardo Feijão (baixo acústico), Nils Wekstein e Jonathan Edo (bateria e percussões).
|
||||
|
||||
Atualmente, Anissá Bensalah apresenta, no palco, seu segundo disco _Sovaj_ (lançado pela Ovastand), na França e no exterior. É acompanhada por Tim Campanella (bateria), Philippe Monge (baixo e keyboard) e Frédéric Antetomaso (guitarra).
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: ABOUT
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
@@ -5,3 +5,4 @@ content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: BIOGRAFÍA
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
|
Before Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 475 KiB |
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: Matriz
|
||||
image_align: left
|
||||
---
|
||||
|
After Width: | Height: | Size: 534 KiB |
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: sovaj
|
||||
media_order: 'jazzradio-logo-copie_0_0_0.jpg,150px-montecarlodoualiya_2013.svg_0_0.png,fr2.jpg,frinter.jpg,logo_france_info_1_0.png,logo-lesinrocks_0.jpg,photohome.JPG'
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- white
|
||||
---
|
||||
|
||||

|
||||
October 2013 - label Ovastand, distribution Musicast (France)
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
---
|
||||
title: sovaj
|
||||
media_order: 'jazzradio-logo-copie_0_0_0.jpg,150px-montecarlodoualiya_2013.svg_0_0.png,fr2.jpg,frinter.jpg,logo_france_info_1_0.png,logo-lesinrocks_0.jpg,photohome.JPG'
|
||||
media_order: 'jazzradio-logo-copie_0_0_0.jpg,150px-montecarlodoualiya_2013.svg_0_0.png,fr2.jpg,frinter.jpg,logo_france_info_1_0.png,logo-lesinrocks_0.jpg,photohome.JPG,cover_Matriz.jpg'
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- white
|
||||
---
|
||||
|
||||

|
||||

|
||||
octobre 2013 - label Ovastand, distribution Musicast
|
||||
|
||||

|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
@@ -21,19 +22,12 @@ voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
@@ -46,3 +40,9 @@ voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: sovaj
|
||||
media_order: 'jazzradio-logo-copie_0_0_0.jpg,150px-montecarlodoualiya_2013.svg_0_0.png,fr2.jpg,frinter.jpg,logo_france_info_1_0.png,logo-lesinrocks_0.jpg,photohome.JPG'
|
||||
image_align: left
|
||||
taxonomy:
|
||||
tag:
|
||||
- white
|
||||
---
|
||||
|
||||

|
||||
Outubro 2013 - gravadora Ovastand, distribuidora Musicast (França)
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||

|
||||
live sur France 2
|
||||
voir la vidéo - Lire l'article
|
||||
Présentée par Thierry Beccaro et Myriam Serrat
|
||||
Diffusée le 25-26 juillet 2014 à 00h
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Press
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
_"Son timbre de voix chatoyant lui permet de passer avec un charme fou du portugais à
|
||||
l’arabe. Avec son premier album « Matriz », Anissá Bensalah dresse un panorama afro
|
||||
brésilien bien à elle."_
|
||||
Remy Kolpa Kopoul, Radio Nova
|
||||
|
||||
_"Il n’est pas donné à tout le monde d’avoir une voix qui évoque une présence « physique » à
|
||||
la simple écoute d’un disque. Anissá est de ces artistes là !"_
|
||||
Nadia Khouri – Dhager, Babelmed.net
|
||||
@@ -6,7 +6,11 @@ content:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
« Elle chante le forró avec un cœur vaudou,
|
||||
arrange une chanson arabe façon bossa
|
||||
et invente le jazz andalou au pays du candomblé »
|
||||
Anne Berthod, Télérama
|
||||
_"Son timbre de voix chatoyant lui permet de passer avec un charme fou du portugais à
|
||||
l’arabe. Avec son premier album « Matriz », Anissá Bensalah dresse un panorama afro
|
||||
brésilien bien à elle."_
|
||||
Remy Kolpa Kopoul, Radio Nova
|
||||
|
||||
_"Il n’est pas donné à tout le monde d’avoir une voix qui évoque une présence « physique » à
|
||||
la simple écoute d’un disque. Anissá est de ces artistes là !"_
|
||||
Nadia Khouri – Dhager, Babelmed.net
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: IMPRENSA
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
_«Seu timbre de voz vibrante e feiticeiro permite que ela transite, com um charme indescritivel, do português ao arabe. Com seu primeiro album " Matriz ", Anissá Bensalah nos faz descobrir o universo afro-brasileiro que é o seu.»_
|
||||
Remy Kolpa Kopoul, Radio Nova (Paris, França)
|
||||
|
||||
_«Nem todo mundo é dotado de uma voz que evoca uma presença "fisica", através da simples escuta de um disco. Anissá é uma dessas artistas.»_
|
||||
Nadia Khouri – Dhager, Babelmed.net (Marseille, França)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
---
|
||||
|
||||
|
||||
02/01/2019 - [Le Grillen](http://www.grillen.fr/) - Colmar (FR)
|
||||
|
||||
04/03/2019 - [Café de la Danse](http://www.cafedeladanse.com/) - Paris (FR)
|
||||
|
||||
|
||||
|
||||
@@ -3,15 +3,9 @@ title: sovaj
|
||||
image_align: left
|
||||
---
|
||||
|
||||
**Date de l'album sovaj**
|
||||
**Vendredi 01 Février 2019**
|
||||
**[Le Grillen](http://www.grillen.fr/)**
|
||||
**Colmar (FR)**
|
||||
|
||||
03/04/2019 - [Café de la Danse](http://www.cafedeladanse.com/) - Paris (FR)
|
||||
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
date(j/m/a) - nom de la salle - ville/pays - [billets](billets)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: sovaj
|
||||
image_align: left
|
||||
---
|
||||
|
||||
|
||||
01/02/2019 - [Le Grillen](http://www.grillen.fr/) - Colmar (FR)
|
||||
|
||||
03/04/2019 - [Café de la Danse](http://www.cafedeladanse.com/) - Paris (FR)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 'Tour dates'
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: Prochaines dates
|
||||
title: 'Prochaines dates'
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: AGENDA
|
||||
image_align: left
|
||||
content:
|
||||
items:
|
||||
- '@self.modular'
|
||||
---
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Contact
|
||||
image_align: left
|
||||
---
|
||||
|
||||
**BOOKING**
|
||||
Ovastand
|
||||
Frederic
|
||||
19, rue Edgar Quinet 92120 Montrouge (France)
|
||||
[fred@ovastand.com](mailo:fred@ovastand.com)
|
||||
+33 (0) 6 31 02 30 53
|
||||
+33 (0) 9 86 41 51 44
|
||||
|
||||
|
||||
|
||||
**PRESS ATTACHÉ**
|
||||
Accent Presse
|
||||
Simon Vessyère
|
||||
8, rue André Messager 75018 Paris (France)
|
||||
[simon@accent-presse.com](mailo:simon@accent-presse.com)
|
||||
+33 (0) 6 70 21 32 83
|
||||
+33 (0) 1 42 57 92 84
|
||||
@@ -5,16 +5,18 @@ image_align: left
|
||||
|
||||
**BOOKING**
|
||||
Ovastand
|
||||
Frédéric Ovastand
|
||||
Frédéric
|
||||
19, rue Edgar Quinet 92120 Montrouge (France)
|
||||
fred@ovastand.com
|
||||
[fred@ovastand.com](mailto:fred@ovastand.com)
|
||||
+33 (0) 6 31 02 30 53
|
||||
+33 (0) 9 86 41 51 44
|
||||
|
||||
|
||||
|
||||
**ATTACHÉ DE PRESSE**
|
||||
Accent Presse
|
||||
Simon Vessyère
|
||||
8, rue André Messager 75018 Paris (France)
|
||||
simon@accent-presse.com
|
||||
+33 (0) 1 42 57 92 84
|
||||
+33 (0) 6 70 21 32 83
|
||||
[simon@accent-presse.com](mailo:simon@accent-presse.com)
|
||||
+33 (0) 6 70 21 32 83
|
||||
+33 (0) 1 42 57 92 84
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: CONTATO
|
||||
image_align: left
|
||||
---
|
||||
|
||||
**CONTRATAÇÃO DE SHOWS**
|
||||
|
||||
Ovastand (nom cliquabe qui nous renvoie à www.ovastand.net)
|
||||
Frederic
|
||||
19, rue Edgar Quinet 92120 Montrouge (França)
|
||||
[fred@ovastand.com](mailo:fred@ovastand.com)
|
||||
+33 (0) 6 31 02 30 53
|
||||
+33 (0) 9 86 41 51 44
|
||||
|
||||
|
||||
|
||||
**ASSESSORIA DE IMPRENSA**
|
||||
|
||||
Accent Presse (nom cliquabe qui nous renvoie à www.accent-presse.com)
|
||||
Simon Vessyère
|
||||
8, rue André Messager 75018 Paris (França)
|
||||
[simon@accent-presse.com](mailo:simon@accent-presse.com)
|
||||
+33 (0) 6 70 21 32 83
|
||||
+33 (0) 1 42 57 92 84
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Home
|
||||
media_order: 'photohome.JPG,fb.svg,yt.svg'
|
||||
content:
|
||||
items: '@self.modular'
|
||||
body_classes: 'title-center title-h1h2'
|
||||
---
|
||||
|
||||
#sovaj
|
||||
|
||||
New album SOVAJ
|
||||
Available in march 2019
|
||||
[itune](itune) - [fnac](fnac) - [amazon](fnac)
|
||||
[deezer](fnac) - [spotify](fnac)
|
||||
[](www.facebook.com) [](www.youtube.com)
|
||||
@@ -2,15 +2,14 @@
|
||||
title: Home
|
||||
media_order: 'photohome.JPG,fb.svg,yt.svg'
|
||||
content:
|
||||
items:
|
||||
- '@self.children'
|
||||
- '@self.modular'
|
||||
items: '@self.modular'
|
||||
body_classes: 'title-center title-h1h2'
|
||||
---
|
||||
|
||||
#sovaj
|
||||
|
||||
album sovaj est disponible sur :
|
||||
Nouvel album SOVAJ
|
||||
Disponible en mars 2019
|
||||
[itune](itune) - [fnac](fnac) - [amazon](fnac)
|
||||
[deezer](fnac) - [spotify](fnac)
|
||||
[](www.facebook.com) [](www.youtube.com)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Home
|
||||
media_order: 'photohome.JPG,fb.svg,yt.svg'
|
||||
content:
|
||||
items: '@self.children, @self.modular'
|
||||
body_classes: 'title-center title-h1h2'
|
||||
---
|
||||
|
||||
#sovaj
|
||||
|
||||
Novo álbum SOVAJ
|
||||
Disponível em março 2019
|
||||
[itune](itune) - [fnac](fnac) - [amazon](fnac)
|
||||
[deezer](fnac) - [spotify](fnac)
|
||||
[]([www.facebook.com](www.facebook.com/anissabensalah.officiel/)) []([www.youtube.com](www.youtube.com/watch?v=OrspkA-bp3Q))
|
||||
|
After Width: | Height: | Size: 284 KiB |
@@ -0,0 +1,18 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# 2 space indentation
|
||||
[*.yaml, *.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,14 @@
|
||||
themes/grav/.sass-cache
|
||||
.DS_Store
|
||||
|
||||
# Node Modules
|
||||
**/node_modules/**
|
||||
themes/grav/js/admin.js
|
||||
themes/grav/js/vendor.js
|
||||
themes/grav/js/*.map
|
||||
.idea
|
||||
|
||||
tests/_output/*
|
||||
tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
@@ -1,3 +1,136 @@
|
||||
# v1.8.14
|
||||
## 11/12/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Grav core update potentially spinning forever because of an error which happens after a successful upgrade
|
||||
* Saving in expert mode can cause `undefined index: header` error [#1537](https://github.com/getgrav/grav-plugin-admin/issues/1537)
|
||||
|
||||
# v1.8.13
|
||||
## 11/05/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added new `|nested()` Twig filter to access array objects with dot notation syntax
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with complex lists structure and nested dot-notation [admin#2236](https://github.com/getgrav/grav/issues/2236)
|
||||
|
||||
# v1.8.12
|
||||
## 10/24/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Updated various lang strings
|
||||
* Removed duplicate lang strings
|
||||
1. [](#bugfix)
|
||||
* Fix XSS checking when empty content [#1533](https://github.com/getgrav/grav-plugin-admin/issues/1533)
|
||||
* Fix DirectInstall not working [#1535](https://github.com/getgrav/grav-plugin-admin/issues/1535)
|
||||
|
||||
# v1.8.11
|
||||
## 10/08/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Change usage of basename where possible [#1480](https://github.com/getgrav/grav-plugin-admin/pull/1480)
|
||||
* Improved filename validation (requires Grav 1.5.3)
|
||||
* Updated various lang codes
|
||||
1. [](#bugfix)
|
||||
* File Uploads: Do not trust mimetype sent by the browser
|
||||
* Fixed file extension detection
|
||||
* Fix for HTML entities in page slug [#1524](https://github.com/getgrav/grav-plugin-admin/issues/1524)
|
||||
* Fix for port in backup download links [#1521](https://github.com/getgrav/grav-plugin-admin/issues/1521)
|
||||
|
||||
# v1.8.10
|
||||
## 10/01/2018
|
||||
|
||||
1. [](#new)
|
||||
* IMPORTANT: Non `admin.super` users are now subject to XSS validation in Page content. Configurable via Configuration / Security
|
||||
* New XSS content warnings and integration into page save
|
||||
* Added new event `onAdminPage()` which allows plugins to customize `Page` object in `$event['page']`
|
||||
1. [](#improved)
|
||||
* Use `Url:post()` to get the `$_POST` variable (allows common security checks/filtering for the POST data)
|
||||
* Requires Grav 1.5.2
|
||||
1. [](#bugfix)
|
||||
* Fixed redirect to correct URL after failed login
|
||||
* Fixed issue in `filepicker` where missing images would cause a loop to try to load them
|
||||
* Twig 2 compatibility fixes for macros
|
||||
* Updated `composer.json` to better match Grav 1.5
|
||||
* Remove `package-lock.json` as it was referencing an insecure JS package
|
||||
|
||||
# v1.8.9
|
||||
## 08/23/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Make order field to use context, not data
|
||||
* Switched to new Grav Yaml class to support Native + Fallback YAML libraries
|
||||
* Minor fix for `file` thumbnails display
|
||||
* Requires Grav 1.5.1
|
||||
|
||||
# v1.8.8
|
||||
## 08/17/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Support URI Params and Query attributes in Login redirect
|
||||
* Added support for textarea value type in `array` field
|
||||
* Added some new lang strings for Grav 1.5.0
|
||||
1. [](#bugfix)
|
||||
* Support params and querystring in login redirect
|
||||
* Added field name nesting with tab field
|
||||
|
||||
# v1.8.7
|
||||
## 07/31/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for deleting 'extra' media files [grav#2100](https://githubcom/getgrav/grav/issues/2100)
|
||||
|
||||
# v1.8.6
|
||||
## 07/13/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Force `html` for markdown preview [grav#2066](https://github.com/getgrav/grav/issues/2066)
|
||||
* Add missing `authorizeTask()` checks in controller [#1483](https://github.com/getgrav/grav/issues/1483)
|
||||
* Add support for `force_ssl` to admin URLs [#1479](https://github.com/getgrav/grav-plugin-admin/issues/1479)
|
||||
|
||||
# v1.8.5
|
||||
## 06/20/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed broken folder attribute on filepicker [#1465](https://github.com/getgrav/grav-plugin-admin/issues/1465)
|
||||
* Added translation for system.session.initialize
|
||||
* Slight updates on new translation strings
|
||||
|
||||
# v1.8.4
|
||||
## 06/11/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Including EXIF JS library in the modules dependencies to fix orientation when uploading images
|
||||
1. [](#bugfix)
|
||||
* Initialize session on setup [#1451](https://github.com/getgrav/grav-plugin-admin/issues/1451)
|
||||
* Force a `null` order when empty in the post request
|
||||
* Fixed some 2FA form styling issues
|
||||
|
||||
# v1.8.3
|
||||
## 05/31/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for selectize plugins as options in the selectize field
|
||||
1. [](#bugfix)
|
||||
* Fixed deep linking in admin after login [#1456](https://github.com/getgrav/grav-plugin-admin/issues/1456)
|
||||
* Fixed Undefined property: `stdClass::$image` in v1.8.2 [#1454](https://github.com/getgrav/grav-plugin-admin/issues/1454)
|
||||
* Pass media order when calling `task:listmedia`
|
||||
|
||||
# v1.8.2
|
||||
## 05/24/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added custom object support for filepicker field
|
||||
* Don't allow saving of a user with no local account file
|
||||
* Controls for `list` field were not in sync between top and bottom
|
||||
1. [](#improved)
|
||||
* More subtle `fieldset` styling
|
||||
1. [](#bugfix)
|
||||
* Check if `$object->blueprints()` exists in `onAdminAfterSave`
|
||||
* When creating first user, check `admin.login` not `site.login`
|
||||
* Fix admin login redirects for multisite setups
|
||||
* Fixed issue with filepicker field where images wouldn't properly merge with the current value if in a page header
|
||||
* Fixed media delete for streams
|
||||
|
||||
# v1.8.1
|
||||
## 05/15/2018
|
||||
|
||||
@@ -18,7 +151,7 @@
|
||||
1. [](#improved)
|
||||
* Added support for JavaScript `bottom` block [#1425](https://github.com/getgrav/grav-plugin-admin/pull/1425)
|
||||
* Added better typography styling for blockquote and markdown in `display` field
|
||||
* Vendor updates
|
||||
* Vendor updates
|
||||
1. [](#bugfix)
|
||||
* Added missing MarkdownExtra strings [#1385](https://github.com/getgrav/grav-plugin-admin/pull/1385)
|
||||
* Updated `blueprints.yaml` with missing `step` attribute [#1415](https://github.com/getgrav/grav-plugin-admin/pull/1415)
|
||||
@@ -46,7 +179,7 @@
|
||||
* Implemented workaround for required text fields [#1390](https://github.com/getgrav/grav-plugin-admin/issues/1390)
|
||||
* Fixed highlight color in Firefox [getgrav/grav#1949](https://github.com/getgrav/grav/issues/1949)
|
||||
* Fix for bad redirect on saving simplesearch (possibly others)
|
||||
|
||||
|
||||
# v1.7.2
|
||||
## 03/21/2018
|
||||
|
||||
@@ -58,8 +191,8 @@
|
||||
* Optimization to stop admin for looking for pages in disabled plugins
|
||||
* Added configuration option to choose if you want to use new 'inline' preview or `new tab'
|
||||
1. [](#bugfix)
|
||||
* Fix redirect bug when changing admin route to `admin-*`
|
||||
* Changed Twig `|count` to `|length` filter [#1391](https://github.com/getgrav/grav-plugin-admin/issues/1391)
|
||||
* Fix redirect bug when changing admin route to `admin-*`
|
||||
* Changed Twig `|count` to `|length` filter [#1391](https://github.com/getgrav/grav-plugin-admin/issues/1391)
|
||||
* Fix for page preview when `HTTP_REFERRER` is not set [grav#1930](https://github.com/getgrav/grav/issues/1930)
|
||||
|
||||
# v1.7.1
|
||||
@@ -94,7 +227,7 @@
|
||||
* Fixed issue with default lang translation in admin [#1361](https://github.com/getgrav/grav-plugin-admin/issues/1361)
|
||||
* Typos in `Tools` -> `Direct Install` page [#1345](https://github.com/getgrav/grav-plugin-admin/issues/1345)
|
||||
* Fixed bug with frontmatter being killed when in `Expert Mode` [#1354](https://github.com/getgrav/grav-plugin-admin/issues/1354)
|
||||
|
||||
|
||||
# v1.7.0-rc.3
|
||||
## 02/15/2018
|
||||
|
||||
@@ -119,7 +252,7 @@
|
||||
* Tidied Tools panel and added translations
|
||||
* Tooltip and new icon for site preview
|
||||
* Updated JS library dependencies
|
||||
* Changed CodeMirror editor to use sans-serif font for readability
|
||||
* Changed CodeMirror editor to use sans-serif font for readability
|
||||
1. [](#bugfix)
|
||||
* Fixed z-index issue in fullscreen mode [#1317](https://github.com/getgrav/grav-plugin-admin/issues/1317)
|
||||
|
||||
@@ -215,7 +348,7 @@
|
||||
* Fixed fields not working in Microsoft Edge with Selectize.js [#1222](https://github.com/getgrav/grav-plugin-admin/pull/1222)
|
||||
* Replaced a left-over "is empty" check [#1232](https://github.com/getgrav/grav-plugin-admin/pull/1232)
|
||||
* Fixed headers drop-down in editor to align properly
|
||||
|
||||
|
||||
|
||||
# v1.6.0
|
||||
## 09/07/2017
|
||||
|
||||
@@ -144,6 +144,7 @@ class AdminPlugin extends Plugin
|
||||
|
||||
// Only activate admin if we're inside the admin path.
|
||||
if ($this->isAdminPath()) {
|
||||
$this->grav['session']->init();
|
||||
$this->active = true;
|
||||
|
||||
// Set cache based on admin_cache option
|
||||
@@ -249,7 +250,7 @@ class AdminPlugin extends Plugin
|
||||
unset($this->grav['user']);
|
||||
$this->grav['user'] = $user;
|
||||
$user->authenticated = true;
|
||||
$user->authorized = $user->authorize('site.login');
|
||||
$user->authorized = $user->authorize('admin.login');
|
||||
|
||||
$messages = $this->grav['messages'];
|
||||
$messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
|
||||
@@ -316,6 +317,16 @@ class AdminPlugin extends Plugin
|
||||
*/
|
||||
public function onPagesInitialized()
|
||||
{
|
||||
$config = $this->config;
|
||||
|
||||
// Force SSL with redirect if required
|
||||
if ($config->get('system.force_ssl')) {
|
||||
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
|
||||
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
$this->grav->redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
$this->session = $this->grav['session'];
|
||||
|
||||
// Set original route for the home page.
|
||||
@@ -349,7 +360,10 @@ class AdminPlugin extends Plugin
|
||||
}
|
||||
|
||||
// Make local copy of POST.
|
||||
$post = !empty($_POST) ? $_POST : [];
|
||||
$post = $this->grav['uri']->post();
|
||||
|
||||
// Initialize Page Types
|
||||
Pages::types();
|
||||
|
||||
// Handle tasks.
|
||||
$this->admin->task = $task = !empty($post['task']) ? $post['task'] : $this->uri->param('task');
|
||||
@@ -373,7 +387,17 @@ class AdminPlugin extends Plugin
|
||||
$page = new Page;
|
||||
$page->expires(0);
|
||||
|
||||
// First look in the pages provided by the Admin plugin itself
|
||||
if ($this->grav['user']->authorize('admin.login')) {
|
||||
$event = new Event(['page' => $page]);
|
||||
$event = $this->grav->fireEvent('onAdminPage', $event);
|
||||
$page = $event['page'];
|
||||
|
||||
if ($page->slug()) {
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
|
||||
// Look in the pages provided by the Admin plugin itself
|
||||
if (file_exists(__DIR__ . "/pages/admin/{$self->template}.md")) {
|
||||
$page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$self->template}.md"));
|
||||
$page->slug(basename($self->template));
|
||||
@@ -456,7 +480,9 @@ class AdminPlugin extends Plugin
|
||||
$twig->twig_vars['location'] = $this->template;
|
||||
$twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/';
|
||||
$twig->twig_vars['admin_route'] = trim($this->admin_route, '/');
|
||||
$twig->twig_vars['current_route'] = '/' . $twig->twig_vars['admin_route'] . '/' . $this->template . '/' . $this->route;
|
||||
$twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route'];
|
||||
$twig->twig_vars['current_url'] = rtrim($twig->twig_vars['base_url_relative'] . '/' . $this->template . '/' . $this->route, '/');
|
||||
$theme_url = '/' . ltrim($this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme,
|
||||
false), '/');
|
||||
$twig->twig_vars['theme_url'] = $theme_url;
|
||||
@@ -762,7 +788,8 @@ class AdminPlugin extends Plugin
|
||||
{
|
||||
// Special case to redirect after changing the admin route to avoid 'breaking'
|
||||
$obj = $event['object'];
|
||||
if (null !== $obj) {
|
||||
|
||||
if (null !== $obj && method_exists($obj, 'blueprints')) {
|
||||
$blueprint = $obj->blueprints()->getFilename();
|
||||
|
||||
if ($blueprint === 'admin/blueprints' && isset($obj->route) && $this->admin_route !== $obj->route) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Admin Panel
|
||||
version: 1.8.1
|
||||
version: 1.8.14
|
||||
description: Adds an advanced administration panel to manage your site
|
||||
icon: empire
|
||||
author:
|
||||
@@ -13,7 +13,7 @@ docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
- { name: grav, version: '>=1.4.5' }
|
||||
- { name: grav, version: '>=1.5.3' }
|
||||
- { name: form, version: '>=2.14.0' }
|
||||
- { name: login, version: '>=2.7.0' }
|
||||
- { name: email, version: '>=2.7.0' }
|
||||
|
||||
@@ -18,6 +18,9 @@ form:
|
||||
title: PLUGIN_ADMIN.CONTENT
|
||||
|
||||
fields:
|
||||
xss_check:
|
||||
type: xss
|
||||
|
||||
frontmatter:
|
||||
classes: frontmatter
|
||||
type: editor
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
namespace Grav\Plugin\Admin\Twig;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
|
||||
class AdminTwigExtension extends \Twig_Extension
|
||||
{
|
||||
@@ -32,6 +31,7 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
new \Twig_SimpleFilter('toYaml', [$this, 'toYamlFilter']),
|
||||
new \Twig_SimpleFilter('fromYaml', [$this, 'fromYamlFilter']),
|
||||
new \Twig_SimpleFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
|
||||
new \Twig_SimpleFilter('nested', [$this, 'nestedFilter']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -43,6 +43,23 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
];
|
||||
}
|
||||
|
||||
public function nestedFilter($current, $name)
|
||||
{
|
||||
$path = explode('.', trim($name, '.'));
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (is_object($current) && isset($current->{$field})) {
|
||||
$current = $current->{$field};
|
||||
} elseif (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
public function cloneFunc($obj)
|
||||
{
|
||||
return clone $obj;
|
||||
@@ -81,7 +98,7 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
return $this->grav['admin']->translate($args, $lang);
|
||||
}
|
||||
|
||||
public function toYamlFilter($value, $inline = true)
|
||||
public function toYamlFilter($value, $inline = null)
|
||||
{
|
||||
return Yaml::dump($value, $inline);
|
||||
|
||||
@@ -89,8 +106,7 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
|
||||
public function fromYamlFilter($value)
|
||||
{
|
||||
$yaml = new Parser();
|
||||
return $yaml->parse($value);
|
||||
return Yaml::parse($value);
|
||||
}
|
||||
|
||||
public function adminNicetimeFilter($date, $long_strings = true)
|
||||
|
||||
@@ -27,7 +27,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceIterator;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
use RocketTheme\Toolbox\Session\Session;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Grav\Common\Yaml;
|
||||
use Composer\Semver\Semver;
|
||||
use PicoFeed\Reader\Reader;
|
||||
|
||||
@@ -170,11 +170,11 @@ class Admin
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach (new \DirectoryIterator($path) as $file) {
|
||||
if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getBasename(), '.')) {
|
||||
if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getFilename(), '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lang = basename($file->getBasename(), '.yaml');
|
||||
$lang = $file->getBasename('.yaml');
|
||||
|
||||
$languages[$lang] = LanguageCodes::getNativeName($lang);
|
||||
|
||||
@@ -202,7 +202,7 @@ class Admin
|
||||
if ($file->isDir() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) {
|
||||
continue;
|
||||
}
|
||||
$configurations[] = basename($file->getBasename(), '.yaml');
|
||||
$configurations[] = $file->getBasename('.yaml');
|
||||
}
|
||||
|
||||
return $configurations;
|
||||
@@ -365,7 +365,7 @@ class Admin
|
||||
|
||||
$userKey = isset($credentials['username']) ? (string)$credentials['username'] : '';
|
||||
$ipKey = Uri::ip();
|
||||
$redirect = isset($post['redirect']) ? $post['redirect'] : $this->uri->route();
|
||||
$redirect = isset($post['redirect']) ? $post['redirect'] : $this->base . $this->route;
|
||||
|
||||
// Check if the current IP has been used in failed login attempts.
|
||||
$attempts = count($rateLimiter->getAttempts($ipKey, 'ip'));
|
||||
@@ -392,11 +392,9 @@ class Admin
|
||||
if ($user->authorized) {
|
||||
$event->defMessage('PLUGIN_ADMIN.LOGIN_LOGGED_IN', 'info');
|
||||
|
||||
$event->defRedirect($redirect);
|
||||
$event->defRedirect(isset($post['redirect']) ? $post['redirect'] : $redirect);
|
||||
} else {
|
||||
$this->session->redirect = $redirect;
|
||||
|
||||
$event->defRedirect($this->uri->route());
|
||||
}
|
||||
} else {
|
||||
if ($user->authorized) {
|
||||
@@ -406,7 +404,7 @@ class Admin
|
||||
}
|
||||
}
|
||||
|
||||
$event->defRedirect($this->uri->route());
|
||||
$event->defRedirect($redirect);
|
||||
|
||||
$message = $event->getMessage();
|
||||
if ($message) {
|
||||
@@ -597,7 +595,8 @@ class Admin
|
||||
}
|
||||
|
||||
if (!$post) {
|
||||
$post = isset($_POST['data']) ? $_POST['data'] : [];
|
||||
$post = $this->grav['uri']->post();
|
||||
$post = isset($post['data']) ? $post['data'] : [];
|
||||
}
|
||||
|
||||
// Check to see if a data type is plugin-provided, before looking into core ones
|
||||
@@ -671,10 +670,10 @@ class Admin
|
||||
$obj->file = $file;
|
||||
$obj->page = $this->grav['pages']->get(dirname($obj->path));
|
||||
|
||||
$filename = pathinfo($obj->title)['filename'];
|
||||
$filename = str_replace(['@3x', '@2x'], '', $filename);
|
||||
if (isset(pathinfo($obj->title)['extension'])) {
|
||||
$filename .= '.' . pathinfo($obj->title)['extension'];
|
||||
$fileInfo = pathinfo($obj->title);
|
||||
$filename = str_replace(['@3x', '@2x'], '', $fileInfo['filename']);
|
||||
if (isset($fileInfo['extension'])) {
|
||||
$filename .= '.' . $fileInfo['extension'];
|
||||
}
|
||||
|
||||
if ($obj->page && isset($obj->page->media()[$filename])) {
|
||||
@@ -1420,6 +1419,9 @@ class Admin
|
||||
$path = "/{$path}";
|
||||
}
|
||||
|
||||
// Fix for entities in path causing looping...
|
||||
$path = urldecode($path);
|
||||
|
||||
$page = $path ? $pages->dispatch($path, true) : $pages->root();
|
||||
|
||||
if (!$page) {
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Theme;
|
||||
@@ -199,6 +202,29 @@ class AdminBaseController
|
||||
$this->redirectCode = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JSON response and terminates the call.
|
||||
*
|
||||
* @param array $response
|
||||
* @param int $code
|
||||
* @return bool
|
||||
*/
|
||||
protected function sendJsonResponse(array $response, $code = 200)
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// JSON response.
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
echo json_encode($response);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ajax upload for files.
|
||||
* Stores in a flash object the temporary file and deals with potential file errors.
|
||||
@@ -225,10 +251,10 @@ class AdminBaseController
|
||||
|
||||
$upload = $this->normalizeFiles($_FILES['data'], $settings->name);
|
||||
|
||||
$filename = trim($upload->file->name);
|
||||
$filename = $upload->file->name;
|
||||
|
||||
// Handle bad filenames.
|
||||
if (strtr($filename, "\t\n\r\0\x0b", '_____') !== $filename || rtrim($filename, '. ') !== $filename || preg_match('|\.php|', $filename)) {
|
||||
if (!Utils::checkFilename($filename)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
|
||||
@@ -259,7 +285,7 @@ class AdminBaseController
|
||||
}
|
||||
|
||||
// Handle errors and breaks without proceeding further
|
||||
if ($upload->file->error != UPLOAD_ERR_OK) {
|
||||
if ($upload->file->error !== UPLOAD_ERR_OK) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
|
||||
@@ -285,6 +311,9 @@ class AdminBaseController
|
||||
$accepted = false;
|
||||
$errors = [];
|
||||
|
||||
// Do not trust mimetype sent by the browser
|
||||
$mime = Utils::getMimeByFilename($upload->file->name);
|
||||
|
||||
foreach ((array)$settings->accept as $type) {
|
||||
// Force acceptance of any file when star notation
|
||||
if ($type === '*') {
|
||||
@@ -293,15 +322,24 @@ class AdminBaseController
|
||||
}
|
||||
|
||||
$isMime = strstr($type, '/');
|
||||
$find = str_replace('*', '.*', $type);
|
||||
$find = str_replace(['.', '*'], ['\.', '.*'], $type);
|
||||
|
||||
$match = preg_match('#' . $find . '$#', $isMime ? $upload->file->type : $upload->file->name);
|
||||
if (!$match) {
|
||||
$message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension';
|
||||
$errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.';
|
||||
$accepted |= false;
|
||||
if ($isMime) {
|
||||
$match = preg_match('#' . $find . '$#', $mime);
|
||||
if (!$match) {
|
||||
$errors[] = 'The MIME type "' . $mime . '" for the file "' . $upload->file->name . '" is not an accepted.';
|
||||
} else {
|
||||
$accepted = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$accepted |= true;
|
||||
$match = preg_match('#' . $find . '$#', $upload->file->name);
|
||||
if (!$match) {
|
||||
$errors[] = 'The File Extension for the file "' . $upload->file->name . '" is not an accepted.';
|
||||
} else {
|
||||
$accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +404,7 @@ class AdminBaseController
|
||||
|
||||
// Generate random name if required
|
||||
if ($settings->random_name) { // TODO: document
|
||||
$extension = pathinfo($upload->file->name)['extension'];
|
||||
$extension = pathinfo($upload->file->name, PATHINFO_EXTENSION);
|
||||
$upload->file->name = Utils::generateRandomString(15) . '.' . $extension;
|
||||
}
|
||||
|
||||
@@ -717,9 +755,9 @@ class AdminBaseController
|
||||
} else {
|
||||
$new_data = $files;
|
||||
}
|
||||
if (isset($data['header'][$init_key])) {
|
||||
if (isset($obj->header()->{$init_key})) {
|
||||
$obj->modifyHeader($init_key,
|
||||
array_replace_recursive([], $data['header'][$init_key], $new_data));
|
||||
array_replace_recursive([], $obj->header()->{$init_key}, $new_data));
|
||||
} else {
|
||||
$obj->modifyHeader($init_key, $new_data);
|
||||
}
|
||||
@@ -743,30 +781,44 @@ class AdminBaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
|
||||
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
|
||||
$data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
|
||||
|
||||
if (null === $data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (method_exists($data, 'blueprints')) {
|
||||
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
|
||||
} elseif (method_exists($data, 'getBlueprint')) {
|
||||
$settings = $data->getBlueprint()->schema()->getProperty($this->post['name']);
|
||||
}
|
||||
|
||||
if (isset($settings['folder'])) {
|
||||
$folder = $settings['folder'];
|
||||
} else {
|
||||
$folder = '@self';
|
||||
$folder = 'self@';
|
||||
}
|
||||
|
||||
// Do not use self@ outside of pages
|
||||
if ($this->view !== 'pages' && in_array($folder, ['@self', 'self@', '@self@'])) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
|
||||
];
|
||||
if (!$data instanceof MediaInterface) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
|
||||
];
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$media = $data->getMedia();
|
||||
} else {
|
||||
// Set destination
|
||||
$folder = Folder::getRelativePath(rtrim($folder, '/'));
|
||||
$folder = $this->admin->getPagePathFromToken($folder);
|
||||
|
||||
$media = new Media($folder);
|
||||
}
|
||||
|
||||
// Set destination
|
||||
$folder = Folder::getRelativePath(rtrim($folder, '/'));
|
||||
$folder = $this->admin->getPagePathFromToken($folder);
|
||||
|
||||
$media = new Media($folder);
|
||||
$available_files = [];
|
||||
$metadata = [];
|
||||
$thumbs = [];
|
||||
@@ -859,7 +911,29 @@ class AdminBaseController
|
||||
$type = $uri->param('type');
|
||||
$field = $uri->param('field');
|
||||
|
||||
$this->taskRemoveMedia();
|
||||
// Get Blueprint
|
||||
$settings = (object) $this->admin->blueprints($blueprint)->schema()->getProperty($field);
|
||||
|
||||
// Get destination
|
||||
if ($this->grav['locator']->isStream($settings->destination)) {
|
||||
$destination = $this->grav['locator']->findResource($settings->destination, false, true);
|
||||
} else {
|
||||
$destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
|
||||
$destination = $this->admin->getPagePathFromToken($destination);
|
||||
}
|
||||
|
||||
// Not in path
|
||||
if (!Utils::startsWith($path, $destination)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => 'Path not valid for this data type'
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only remove files from correct destination...
|
||||
$this->taskRemoveMedia($destination . '/' . basename($path));
|
||||
|
||||
if ($type === 'pages') {
|
||||
$page = $this->admin->page(true, $proute);
|
||||
@@ -876,9 +950,11 @@ class AdminBaseController
|
||||
|
||||
$page->save();
|
||||
} else {
|
||||
|
||||
$blueprint_prefix = $type === 'config' ? '' : $type . '.';
|
||||
$blueprint_name = str_replace(['config/', '/blueprints'], '', $blueprint);
|
||||
$blueprint_field = $blueprint_prefix . $blueprint_name . '.' . $field;
|
||||
|
||||
$files = $this->grav['config']->get($blueprint_field);
|
||||
|
||||
if ($files) {
|
||||
@@ -919,15 +995,17 @@ class AdminBaseController
|
||||
*
|
||||
* @return bool True if the action was performed
|
||||
*/
|
||||
public function taskRemoveMedia()
|
||||
public function taskRemoveMedia($filename = null)
|
||||
{
|
||||
if (!$this->canEditMedia()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = base64_decode($this->grav['uri']->param('route'));
|
||||
if (!$filename) {
|
||||
$filename = base64_decode($this->route);
|
||||
if (is_null($filename)) {
|
||||
$filename = base64_decode($this->grav['uri']->param('route'));
|
||||
if (!$filename) {
|
||||
$filename = base64_decode($this->route);
|
||||
}
|
||||
}
|
||||
|
||||
$file = File::instance($filename);
|
||||
|
||||
@@ -14,17 +14,18 @@ use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Common\User\User;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Backup\ZipBackup;
|
||||
use Grav\Plugin\Admin\Twig\AdminTwigExtension;
|
||||
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
|
||||
use Grav\Common\Yaml;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
|
||||
/**
|
||||
* Class AdminController
|
||||
@@ -465,7 +466,7 @@ class AdminController extends AdminBaseController
|
||||
/**
|
||||
* Handles updating Grav
|
||||
*
|
||||
* @return bool True if the action was performed
|
||||
* @return bool False if user has no permissions.
|
||||
*/
|
||||
public function taskUpdategrav()
|
||||
{
|
||||
@@ -478,14 +479,14 @@ class AdminController extends AdminBaseController
|
||||
$result = Gpm::selfupgrade();
|
||||
|
||||
if ($result) {
|
||||
$this->admin->json_response = [
|
||||
$json_response = [
|
||||
'status' => 'success',
|
||||
'type' => 'updategrav',
|
||||
'version' => $version,
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
|
||||
];
|
||||
} else {
|
||||
$this->admin->json_response = [
|
||||
$json_response = [
|
||||
'status' => 'error',
|
||||
'type' => 'updategrav',
|
||||
'version' => GRAV_VERSION,
|
||||
@@ -493,7 +494,7 @@ class AdminController extends AdminBaseController
|
||||
];
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->sendJsonResponse($json_response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -611,14 +612,18 @@ class AdminController extends AdminBaseController
|
||||
$reorder = true;
|
||||
$data = (array)$this->data;
|
||||
|
||||
$this->grav['twig']->twig_vars['current_form_data'] = $data;
|
||||
|
||||
// Special handler for user data.
|
||||
if ($this->view === 'user') {
|
||||
if (!$this->grav['user']->exists()) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_USER_EXISTS'),'error');
|
||||
return false;
|
||||
}
|
||||
if (!$this->admin->authorize(['admin.super', 'admin.users'])) {
|
||||
//not admin.super or admin.users
|
||||
// no user file or not admin.super or admin.users
|
||||
if ($this->prepareData($data)->username !== $this->grav['user']->username) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
|
||||
'error');
|
||||
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.','error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -643,13 +648,25 @@ class AdminController extends AdminBaseController
|
||||
// Ensure route is prefixed with a forward slash.
|
||||
$route = '/' . ltrim($route, '/');
|
||||
|
||||
// Check for valid frontmatter
|
||||
if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'),
|
||||
'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// XSS Checks for page content
|
||||
$xss_whitelist = $this->grav['config']->get('security.xss_whitelist', 'admin.super');
|
||||
if (!$this->admin->authorize($xss_whitelist)) {
|
||||
$check_what = ['header' => isset($data['header']) ? $data['header'] : '', 'frontmatter' => isset($data['frontmatter']) ? $data['frontmatter'] : '', 'content' => isset($data['content']) ? $data['content'] : ''];
|
||||
$results = Security::detectXssFromArray($check_what);
|
||||
if (!empty($results)) {
|
||||
$this->admin->setMessage('<i class="fa fa-ban"></i> ' . $this->admin->translate('PLUGIN_ADMIN.XSS_ONSAVE_ISSUE'),
|
||||
'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$parent = $route && $route !== '/' && $route !== '.' && $route !== '/.' ? $pages->dispatch($route, true) : $pages->root();
|
||||
$original_order = (int)trim($obj->order(), '.');
|
||||
@@ -758,16 +775,8 @@ class AdminController extends AdminBaseController
|
||||
public function checkValidFrontmatter($frontmatter)
|
||||
{
|
||||
try {
|
||||
// Try native PECL YAML PHP extension first if available.
|
||||
if (function_exists('yaml_parse')) {
|
||||
$saved = @ini_get('yaml.decode_php');
|
||||
@ini_set('yaml.decode_php', 0);
|
||||
@yaml_parse("---\n" . $frontmatter . "\n...");
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
} else {
|
||||
Yaml::parse($frontmatter);
|
||||
}
|
||||
} catch (ParseException $e) {
|
||||
Yaml::parse($frontmatter);
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -874,6 +883,10 @@ class AdminController extends AdminBaseController
|
||||
|
||||
protected function taskGetNewsFeed()
|
||||
{
|
||||
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cache = $this->grav['cache'];
|
||||
|
||||
if ($this->post['refresh'] === 'true') {
|
||||
@@ -910,11 +923,13 @@ class AdminController extends AdminBaseController
|
||||
} catch (\Exception $e) {
|
||||
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->admin->json_response = ['status' => 'success', 'feed_data' => $feed_data];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -922,6 +937,10 @@ class AdminController extends AdminBaseController
|
||||
*/
|
||||
protected function taskGetUpdates()
|
||||
{
|
||||
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->post;
|
||||
$flush = (isset($data['flush']) && $data['flush'] == true) ? true : false;
|
||||
|
||||
@@ -954,12 +973,17 @@ class AdminController extends AdminBaseController
|
||||
];
|
||||
} else {
|
||||
$this->admin->json_response = ['status' => 'error', 'message' => 'Cannot connect to the GPM'];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -968,12 +992,16 @@ class AdminController extends AdminBaseController
|
||||
*/
|
||||
protected function taskGetNotifications()
|
||||
{
|
||||
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cache = $this->grav['cache'];
|
||||
if (!(bool)$this->grav['config']->get('system.cache.enabled') || !$notifications = $cache->fetch('notifications')) {
|
||||
//No notifications cache (first time)
|
||||
$this->admin->json_response = ['status' => 'success', 'notifications' => [], 'need_update' => true];
|
||||
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$need_update = false;
|
||||
@@ -990,7 +1018,7 @@ class AdminController extends AdminBaseController
|
||||
} catch (\Exception $e) {
|
||||
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->admin->json_response = [
|
||||
@@ -998,6 +1026,8 @@ class AdminController extends AdminBaseController
|
||||
'notifications' => $notifications,
|
||||
'need_update' => $need_update
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1007,6 +1037,10 @@ class AdminController extends AdminBaseController
|
||||
*/
|
||||
protected function taskProcessNotifications()
|
||||
{
|
||||
if (!$this->authorizeTask('notifications', ['admin.login', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cache = $this->grav['cache'];
|
||||
|
||||
$data = $this->post;
|
||||
@@ -1156,8 +1190,8 @@ class AdminController extends AdminBaseController
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
|
||||
];
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
|
||||
return $this->sendJsonResponse($json_response, 403);
|
||||
}
|
||||
|
||||
//check if there are packages that have this as a dependency. Abort and show which ones
|
||||
@@ -1172,8 +1206,8 @@ class AdminController extends AdminBaseController
|
||||
}
|
||||
|
||||
$json_response = ['status' => 'error', 'message' => $message];
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
|
||||
return $this->sendJsonResponse($json_response, 200);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1181,8 +1215,8 @@ class AdminController extends AdminBaseController
|
||||
$result = Gpm::uninstall($package, []);
|
||||
} catch (\Exception $e) {
|
||||
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
|
||||
return $this->sendJsonResponse($json_response, 200);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
@@ -1191,16 +1225,16 @@ class AdminController extends AdminBaseController
|
||||
'dependencies' => $dependencies,
|
||||
'message' => $this->admin->translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')
|
||||
];
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
|
||||
return $this->sendJsonResponse($json_response, 200);
|
||||
}
|
||||
|
||||
$json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.UNINSTALL_FAILED')
|
||||
];
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
|
||||
return $this->sendJsonResponse($json_response, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1215,6 +1249,15 @@ class AdminController extends AdminBaseController
|
||||
$package_name = isset($data['package_name']) ? $data['package_name'] : '';
|
||||
$current_version = isset($data['current_version']) ? $data['current_version'] : '';
|
||||
|
||||
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
|
||||
$json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
|
||||
];
|
||||
|
||||
$this->sendJsonResponse($json_response, 403);
|
||||
}
|
||||
|
||||
$url = "https://getgrav.org/download/{$type}s/$slug/$current_version";
|
||||
|
||||
$result = Gpm::directInstall($url);
|
||||
@@ -1344,7 +1387,7 @@ class AdminController extends AdminBaseController
|
||||
}
|
||||
|
||||
$download = urlencode(base64_encode($backup));
|
||||
$url = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
|
||||
$url = rtrim($this->grav['uri']->rootUrl(false), '/') . '/' . trim($this->admin->base,
|
||||
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
|
||||
|
||||
$log->content([
|
||||
@@ -1574,6 +1617,8 @@ class AdminController extends AdminBaseController
|
||||
{
|
||||
$this->uri = $this->uri ?: $this->grav['uri'];
|
||||
$uri = $this->uri->post('uri');
|
||||
$order = $this->uri->post('order') ?: null;
|
||||
|
||||
if ($uri) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
@@ -1584,8 +1629,11 @@ class AdminController extends AdminBaseController
|
||||
|
||||
$media_path = $page ? $page->path() : null;
|
||||
}
|
||||
if ($order) {
|
||||
$order = array_map('trim', explode(',', $order));
|
||||
}
|
||||
|
||||
return $media_path ? new Media($media_path) : null;
|
||||
return $media_path ? new Media($media_path, $order) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1646,6 +1694,19 @@ class AdminController extends AdminBaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = $_FILES['file']['name'];
|
||||
|
||||
// Handle bad filenames.
|
||||
if (!Utils::checkFilename($filename)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'),
|
||||
$filename, 'Bad filename')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$grav_limit = $config->get('system.media.upload_limit', 0);
|
||||
// You should also check filesize here.
|
||||
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
|
||||
@@ -1659,18 +1720,13 @@ class AdminController extends AdminBaseController
|
||||
|
||||
|
||||
// Check extension
|
||||
$fileParts = pathinfo($_FILES['file']['name']);
|
||||
|
||||
$fileExt = '';
|
||||
if (isset($fileParts['extension'])) {
|
||||
$fileExt = strtolower($fileParts['extension']);
|
||||
}
|
||||
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
|
||||
// If not a supported type, return
|
||||
if (!$fileExt || !$config->get("media.types.{$fileExt}")) {
|
||||
if (!$extension || !$config->get("media.types.{$extension}")) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $fileExt
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension
|
||||
];
|
||||
|
||||
return false;
|
||||
@@ -1687,9 +1743,16 @@ class AdminController extends AdminBaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
$path = $media->path();
|
||||
if ($locator->isStream($path)) {
|
||||
$path = $locator->findResource($path, true, true);
|
||||
}
|
||||
|
||||
// Upload it
|
||||
if (!move_uploaded_file($_FILES['file']['tmp_name'],
|
||||
sprintf('%s/%s', $media->path(), $_FILES['file']['name']))
|
||||
sprintf('%s/%s', $path, $filename))
|
||||
) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
@@ -1701,13 +1764,12 @@ class AdminController extends AdminBaseController
|
||||
|
||||
// Add metadata if needed
|
||||
$include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
|
||||
$filename = $fileParts['basename'];
|
||||
$filename = str_replace(['@3x', '@2x'], '', $filename);
|
||||
$basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
|
||||
|
||||
$metadata = [];
|
||||
|
||||
if ($include_metadata && isset($media[$filename])) {
|
||||
$img_metadata = $media[$filename]->metadata();
|
||||
if ($include_metadata && isset($media[$basename])) {
|
||||
$img_metadata = $media[$basename]->metadata();
|
||||
if ($img_metadata) {
|
||||
$metadata = $img_metadata;
|
||||
}
|
||||
@@ -1750,6 +1812,11 @@ class AdminController extends AdminBaseController
|
||||
|
||||
$filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
|
||||
|
||||
// Handle bad filenames.
|
||||
if (!Utils::checkFilename($filename)) {
|
||||
$filename = null;
|
||||
}
|
||||
|
||||
if (!$filename) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
@@ -1759,7 +1826,13 @@ class AdminController extends AdminBaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
$targetPath = $media->path() . '/' . $filename;
|
||||
if ($locator->isStream($targetPath)) {
|
||||
$targetPath = $locator->findResource($targetPath, true, true);
|
||||
}
|
||||
$fileParts = pathinfo($filename);
|
||||
|
||||
$found = false;
|
||||
@@ -1781,7 +1854,13 @@ class AdminController extends AdminBaseController
|
||||
// Remove Extra Files
|
||||
foreach (scandir($media->path(), SCANDIR_SORT_NONE) as $file) {
|
||||
if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}(?:\.meta\.yaml)?$|{$filename}\.meta\.yaml$/", $file)) {
|
||||
$result = unlink($media->path() . '/' . $file);
|
||||
|
||||
$targetPath = $media->path() . '/' . $file;
|
||||
if ($locator->isStream($targetPath)) {
|
||||
$targetPath = $locator->findResource($targetPath, true, true);
|
||||
}
|
||||
|
||||
$result = unlink($targetPath);
|
||||
|
||||
if (!$result) {
|
||||
$this->admin->json_response = [
|
||||
@@ -1825,9 +1904,9 @@ class AdminController extends AdminBaseController
|
||||
*/
|
||||
protected function taskProcessMarkdown()
|
||||
{
|
||||
/*if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
|
||||
return;
|
||||
}*/
|
||||
if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$page = $this->admin->page(true);
|
||||
@@ -1843,6 +1922,7 @@ class AdminController extends AdminBaseController
|
||||
|
||||
$this->preparePage($page, true);
|
||||
$page->header();
|
||||
$page->templateFormat('html');
|
||||
|
||||
// Add theme template paths to Twig loader
|
||||
$template_paths = $this->grav['locator']->findResources('theme://templates');
|
||||
@@ -2153,6 +2233,10 @@ class AdminController extends AdminBaseController
|
||||
*/
|
||||
protected function taskSwitchlanguage()
|
||||
{
|
||||
if (!$this->authorizeTask('switch language', ['admin.pages', 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = (array)$this->data;
|
||||
|
||||
if (isset($data['lang'])) {
|
||||
@@ -2176,6 +2260,68 @@ class AdminController extends AdminBaseController
|
||||
|
||||
$admin_route = $this->admin->base;
|
||||
$this->setRedirect('/' . $language . $admin_route . '/' . $redirect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle direct install.
|
||||
*/
|
||||
protected function taskDirectInstall()
|
||||
{
|
||||
if (!$this->authorizeTask('install', ['admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_path = isset($this->data['file_path']) ? $this->data['file_path'] : null ;
|
||||
|
||||
if (isset($_FILES['uploaded_file'])) {
|
||||
|
||||
// Check $_FILES['file']['error'] value.
|
||||
switch ($_FILES['uploaded_file']['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
|
||||
return false;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
|
||||
return false;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
|
||||
return false;
|
||||
default:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_path = $_FILES['uploaded_file']['tmp_name'];
|
||||
|
||||
// Handle bad filenames.
|
||||
if (!Utils::checkFilename(basename($file_path))) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$result = Gpm::directInstall($file_path);
|
||||
|
||||
if ($result === true) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
|
||||
} else {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
|
||||
'error');
|
||||
}
|
||||
|
||||
$this->setRedirect('/tools');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2249,49 +2395,5 @@ class AdminController extends AdminBaseController
|
||||
return $filename . '.md';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle direct install.
|
||||
*/
|
||||
protected function taskDirectInstall()
|
||||
{
|
||||
$file_path = isset($this->data['file_path']) ? $this->data['file_path'] : null ;
|
||||
|
||||
if (isset($_FILES['uploaded_file'])) {
|
||||
|
||||
// Check $_FILES['file']['error'] value.
|
||||
switch ($_FILES['uploaded_file']['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
|
||||
return false;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
|
||||
return false;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
|
||||
return false;
|
||||
default:
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_path = $_FILES['uploaded_file']['tmp_name'];
|
||||
}
|
||||
|
||||
|
||||
$result = Gpm::directInstall($file_path);
|
||||
|
||||
if ($result === true) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
|
||||
} else {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
|
||||
'error');
|
||||
}
|
||||
|
||||
$this->setRedirect('/tools');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
{
|
||||
"name": "grav-plugin-admin",
|
||||
"type": "grav-plugin",
|
||||
"description": "Admin plugin for Grav CMS",
|
||||
"keywords": ["admin", "plugin", "manager", "panel"],
|
||||
"homepage": "https://github.com/trilbymedia/grav-plugin-admin",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Team Grav",
|
||||
"email": "devs@getgrav.org",
|
||||
"homepage": "http://getgrav.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.4",
|
||||
"composer/semver": "^1.4",
|
||||
"fguillot/picofeed": "@stable"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.1",
|
||||
"fzaninotto/faker": "^1.5",
|
||||
"symfony/yaml": "~2.8",
|
||||
"symfony/console": "~2.8",
|
||||
"symfony/finder": "~2.8",
|
||||
"symfony/event-dispatcher": "~2.8"
|
||||
"symfony/yaml": "~3.4",
|
||||
"symfony/console": "~3.4",
|
||||
"symfony/finder": "~3.4",
|
||||
"symfony/event-dispatcher": "~3.4"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.6.4"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vendor/bin/codecept run unit",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"project":"grav-plugin-admin",
|
||||
"platforms":{
|
||||
"grav":{
|
||||
"nodes":{
|
||||
"plugin":[
|
||||
{
|
||||
"source":"/",
|
||||
"destination":"/user/plugins/admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,6 +392,10 @@ PLUGIN_ADMIN:
|
||||
DISPLAY_ERRORS_HELP: "Display full backtrace-style error page"
|
||||
LOG_ERRORS: "Log errors"
|
||||
LOG_ERRORS_HELP: "Log errors to /logs folder"
|
||||
LOG_HANDLER: "Log handler"
|
||||
LOG_HANDLER_HELP: "Where to output the logs"
|
||||
SYSLOG_FACILITY: "Syslog facility"
|
||||
SYSLOG_FACILITY_HELP: "Syslog facility for output"
|
||||
DEBUGGER: "Debugger"
|
||||
DEBUGGER_HELP: "Enable Grav debugger and following settings"
|
||||
DEBUG_TWIG: "Debug Twig"
|
||||
@@ -441,6 +445,7 @@ PLUGIN_ADMIN:
|
||||
PAGE_FILE: "Page Template"
|
||||
PAGE_FILE_HELP: "Page template file name, and by default the display template for this page"
|
||||
NO_USER_ACCOUNTS: "No user accounts found, please create one first..."
|
||||
NO_USER_EXISTS: "No local user exists for this account, cannot save..."
|
||||
REDIRECT_TRAILING_SLASH: "Redirect trailing slash"
|
||||
REDIRECT_TRAILING_SLASH_HELP: "Perform a 301 redirect rather than transparently handling trailing slash URIs."
|
||||
DEFAULT_DATE_FORMAT: "Page date format"
|
||||
@@ -502,7 +507,6 @@ PLUGIN_ADMIN:
|
||||
ADD_FOLDER: "Add Folder"
|
||||
PROXY_URL: "Proxy URL"
|
||||
PROXY_URL_HELP: "Enter the proxy HOST or IP and PORT"
|
||||
INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save"
|
||||
NOTHING_TO_SAVE: "Nothing to Save"
|
||||
FILE_ERROR_ADD: "An error occurred while trying to add the file"
|
||||
FILE_ERROR_UPLOAD: "An error occurred while trying to upload the file"
|
||||
@@ -554,7 +558,6 @@ PLUGIN_ADMIN:
|
||||
CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTE: Page must be saved before you can upload files to it."
|
||||
DROP_FILES_HERE_TO_UPLOAD: "Drop your files here or <strong>click in this area</strong>"
|
||||
INSERT: "Insert"
|
||||
METADATA: "Metadata"
|
||||
UNDO: "Undo"
|
||||
REDO: "Redo"
|
||||
HEADERS: "Headers"
|
||||
@@ -713,3 +716,26 @@ PLUGIN_ADMIN:
|
||||
ROUTE_ALIASES: "Route Aliases"
|
||||
OPEN_NEW_TAB: "Open in new tab"
|
||||
PAGEMEDIA_RESIZER: "> The following settings apply to images uploaded through the page media. Resize Width / Height will automatically resize down and proportionally an image if exceeds the limits set in the inputs. Resolution min and max values define the size ranges for uploaded images. Set the fields to 0 to prevent any manipulation."
|
||||
SESSION_INITIALIZE: "Initialize Session"
|
||||
SESSION_INITIALIZE_HELP: "Makes Grav to start a session. This feature is needed to make any user interaction to work, such as login, forms etc. Admin plugin isn't affected by this setting."
|
||||
STRICT_YAML_COMPAT: "YAML Compatibility"
|
||||
STRICT_YAML_COMPAT_HELP: "Falls back to Symfony 2.4 YAML parser if Native or 3.4 parser fails"
|
||||
STRICT_TWIG_COMPAT: "Twig Compatibility"
|
||||
STRICT_TWIG_COMPAT_HELP: "Enables deprecated Twig autoescape setting. When disabled, |raw filter is required to output HTML as Twig will autoescape output"
|
||||
SECURITY: "Security"
|
||||
XSS_SECURITY: "XSS Security"
|
||||
XSS_WHITELIST_PERMISSIONS: "Whitelist Permissions"
|
||||
XSS_WHITELIST_PERMISSIONS_HELP: "Users with these permissions will skip the XSS rules when saving content"
|
||||
XSS_ON_EVENTS: "On-events"
|
||||
XSS_INVALID_PROTOCOLS: "Enable Invalid protocols"
|
||||
XSS_MOZ_BINDINGS: "Moz bindings"
|
||||
XSS_HTML_INLINE_STYLES: "HTML inline styles"
|
||||
XSS_DANGEROUS_TAGS: "Dangerous HTML tags"
|
||||
XSS_DANGEROUS_TAGS_LIST: "Dangerous HTML tags list"
|
||||
XSS_ONSAVE_ISSUE: "Save failed: XSS issue detected..."
|
||||
XSS_ISSUE: "<strong>NOTICE:</strong> Grav found potential XSS issues in <strong>%s</strong>"
|
||||
UPLOADS_SECURITY: "Uploads Security"
|
||||
UPLOADS_DANGEROUS_EXTENSIONS: "Dangerous Extensions"
|
||||
UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Block these extensions from being uploaded no matter the accepted MIME types"
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ class Template {
|
||||
return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
|
||||
}
|
||||
|
||||
isTextArea() {
|
||||
return this.container.data('grav-array-textarea') || false;
|
||||
}
|
||||
|
||||
shouldBeDisabled() {
|
||||
// check for toggleables, if field is toggleable and it's not enabled, render disabled
|
||||
let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
|
||||
@@ -35,20 +39,22 @@ class Template {
|
||||
}
|
||||
|
||||
getNewRow() {
|
||||
let tpl = '';
|
||||
let tpl = '';const value = this.isTextArea()
|
||||
? `<textarea ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" name="" placeholder="${this.getValuePlaceholder()}"></textarea>`
|
||||
: `<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />`;
|
||||
|
||||
if (this.isValueOnly()) {
|
||||
tpl += `
|
||||
<div class="form-row array-field-value_only" data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" value="" placeholder="${this.getValuePlaceholder()}" />
|
||||
${value}
|
||||
`;
|
||||
} else {
|
||||
tpl += `
|
||||
<div class="form-row" data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
|
||||
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />
|
||||
${value}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import { config, uri_params } from 'grav-config';
|
||||
import request from '../../utils/request';
|
||||
|
||||
const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
|
||||
// const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
|
||||
|
||||
export default class FilePickerField {
|
||||
|
||||
@@ -45,10 +45,11 @@ export default class FilePickerField {
|
||||
let parent = field.closest('[data-grav-filepicker]');
|
||||
let name = parent.data('name');
|
||||
let value = parent.data('value');
|
||||
let params = JSON.stringify(uri_params || '{}');
|
||||
|
||||
request(url, {
|
||||
method: 'post',
|
||||
body: { name }
|
||||
body: { name, params }
|
||||
}, (response) => {
|
||||
if (typeof response.files === 'undefined') {
|
||||
return;
|
||||
@@ -78,14 +79,12 @@ export default class FilePickerField {
|
||||
let renderOption = function renderOption(item, escape) {
|
||||
let image = '';
|
||||
if (imagesPreview && folder && (!item.status || item.status === 'available') && item.name.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
||||
const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
|
||||
const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
|
||||
// const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
|
||||
// const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
|
||||
const source = thumbs[item.name] || `${config.base_url_relative}/../${folder}/${item.name}`;
|
||||
|
||||
image = `
|
||||
<img class="filepicker-field-image"
|
||||
src="${source}"
|
||||
onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';this.onerror='';}else{this.src='${fallback2x}'}" />`;
|
||||
// onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';}else{this.src='${fallback2x}'}"
|
||||
image = `<img class="filepicker-field-image" src="${source}" />`;
|
||||
}
|
||||
|
||||
return `<div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import Dropzone from 'dropzone';
|
||||
import EXIF from 'exif-js';
|
||||
import request from '../../utils/request';
|
||||
import { config, translations } from 'grav-config';
|
||||
|
||||
@@ -70,6 +71,8 @@ const DropzoneMediaConfig = {
|
||||
</div>`.trim()
|
||||
};
|
||||
|
||||
global.EXIF = EXIF;
|
||||
|
||||
const ACCEPT_FUNC = function(file, done, settings) {
|
||||
const resolution = settings.resolution;
|
||||
if (!resolution) return done();
|
||||
@@ -162,7 +165,6 @@ export default class FilesField {
|
||||
}
|
||||
|
||||
getURI() {
|
||||
console.log(this.container.data('mediaUri'));
|
||||
return this.container.data('mediaUri') || '';
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ export default class SelectizeField {
|
||||
let field = (isInput ? element : element.find('input, select'));
|
||||
|
||||
if (!field.length || field.get(0).selectize) { return; }
|
||||
field.selectize($.extend({}, data, {
|
||||
plugins: ['required-fix']
|
||||
}));
|
||||
const plugins = $.merge(data.plugins ? data.plugins : [], ['required-fix']);
|
||||
field.selectize($.extend({}, data, { plugins }));
|
||||
|
||||
this.elements.push(field.data('selectize'));
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ import request from './utils/request';
|
||||
import './utils/2fa';
|
||||
|
||||
// bootstrap jQuery extensions
|
||||
import 'bootstrap/js/transition';
|
||||
import 'bootstrap/js/dropdown';
|
||||
import 'bootstrap/js/collapse';
|
||||
import './utils/bootstrap-transition';
|
||||
import './utils/bootstrap-collapse';
|
||||
|
||||
// tabs memory
|
||||
import './utils/tabs-memory';
|
||||
|
||||
@@ -74,7 +74,8 @@ export default class PageMedia extends FilesField {
|
||||
}
|
||||
|
||||
fetchMedia() {
|
||||
const body = { uri: this.getURI() };
|
||||
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
|
||||
const body = { uri: this.getURI(), order };
|
||||
let url = this.urls.fetch;
|
||||
|
||||
request(url, { method: 'post', body }, (response) => {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: collapse.js v3.4.0
|
||||
* http://getbootstrap.com/javascript/#collapse
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
/* jshint latedef: false */
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// COLLAPSE PUBLIC CLASS DEFINITION
|
||||
// ================================
|
||||
|
||||
var Collapse = function(element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = $.extend({}, Collapse.DEFAULTS, options);
|
||||
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
||||
'[data-toggle="collapse"][data-target="#' + element.id + '"]');
|
||||
this.transitioning = null;
|
||||
|
||||
if (this.options.parent) {
|
||||
this.$parent = this.getParent();
|
||||
} else {
|
||||
this.addAriaAndCollapsedClass(this.$element, this.$trigger);
|
||||
}
|
||||
|
||||
if (this.options.toggle) this.toggle();
|
||||
};
|
||||
|
||||
Collapse.VERSION = '3.4.0';
|
||||
|
||||
Collapse.TRANSITION_DURATION = 350;
|
||||
|
||||
Collapse.DEFAULTS = {
|
||||
toggle: true
|
||||
};
|
||||
|
||||
Collapse.prototype.dimension = function() {
|
||||
var hasWidth = this.$element.hasClass('width');
|
||||
return hasWidth ? 'width' : 'height';
|
||||
};
|
||||
|
||||
Collapse.prototype.show = function() {
|
||||
if (this.transitioning || this.$element.hasClass('in')) return;
|
||||
|
||||
var activesData;
|
||||
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing');
|
||||
|
||||
if (actives && actives.length) {
|
||||
activesData = actives.data('bs.collapse');
|
||||
if (activesData && activesData.transitioning) return;
|
||||
}
|
||||
|
||||
var startEvent = $.Event('show.bs.collapse');
|
||||
this.$element.trigger(startEvent);
|
||||
if (startEvent.isDefaultPrevented()) return;
|
||||
|
||||
if (actives && actives.length) {
|
||||
Plugin.call(actives, 'hide');
|
||||
activesData || actives.data('bs.collapse', null);
|
||||
}
|
||||
|
||||
var dimension = this.dimension();
|
||||
|
||||
this.$element
|
||||
.removeClass('collapse')
|
||||
.addClass('collapsing')[dimension](0)
|
||||
.attr('aria-expanded', true);
|
||||
|
||||
this.$trigger
|
||||
.removeClass('collapsed')
|
||||
.attr('aria-expanded', true);
|
||||
|
||||
this.transitioning = 1;
|
||||
|
||||
var complete = function() {
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse in')[dimension]('');
|
||||
this.transitioning = 0;
|
||||
this.$element
|
||||
.trigger('shown.bs.collapse');
|
||||
};
|
||||
|
||||
if (!$.support.transition) return complete.call(this);
|
||||
|
||||
var scrollSize = $.camelCase(['scroll', dimension].join('-'));
|
||||
|
||||
this.$element
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]);
|
||||
};
|
||||
|
||||
Collapse.prototype.hide = function() {
|
||||
if (this.transitioning || !this.$element.hasClass('in')) return;
|
||||
|
||||
var startEvent = $.Event('hide.bs.collapse');
|
||||
this.$element.trigger(startEvent);
|
||||
if (startEvent.isDefaultPrevented()) return;
|
||||
|
||||
var dimension = this.dimension();
|
||||
|
||||
this.$element[dimension](this.$element[dimension]())[0].offsetHeight;
|
||||
|
||||
this.$element
|
||||
.addClass('collapsing')
|
||||
.removeClass('collapse in')
|
||||
.attr('aria-expanded', false);
|
||||
|
||||
this.$trigger
|
||||
.addClass('collapsed')
|
||||
.attr('aria-expanded', false);
|
||||
|
||||
this.transitioning = 1;
|
||||
|
||||
var complete = function() {
|
||||
this.transitioning = 0;
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.trigger('hidden.bs.collapse');
|
||||
};
|
||||
|
||||
if (!$.support.transition) return complete.call(this);
|
||||
|
||||
this.$element[dimension](0)
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION);
|
||||
};
|
||||
|
||||
Collapse.prototype.toggle = function() {
|
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']();
|
||||
};
|
||||
|
||||
Collapse.prototype.getParent = function() {
|
||||
return $(this.options.parent)
|
||||
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
||||
.each($.proxy(function(i, element) {
|
||||
var $element = $(element);
|
||||
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element);
|
||||
}, this))
|
||||
.end();
|
||||
};
|
||||
|
||||
Collapse.prototype.addAriaAndCollapsedClass = function($element, $trigger) {
|
||||
var isOpen = $element.hasClass('in');
|
||||
|
||||
$element.attr('aria-expanded', isOpen);
|
||||
$trigger
|
||||
.toggleClass('collapsed', !isOpen)
|
||||
.attr('aria-expanded', isOpen);
|
||||
};
|
||||
|
||||
function getTargetFromTrigger($trigger) {
|
||||
var href;
|
||||
var target = $trigger.attr('data-target') ||
|
||||
(href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, ''); // strip for ie7
|
||||
|
||||
return $(target);
|
||||
}
|
||||
|
||||
// COLLAPSE PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function() {
|
||||
var $this = $(this);
|
||||
var data = $this.data('bs.collapse');
|
||||
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
|
||||
|
||||
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false;
|
||||
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)));
|
||||
if (typeof option === 'string') data[option]();
|
||||
});
|
||||
}
|
||||
|
||||
var old = $.fn.collapse;
|
||||
|
||||
$.fn.collapse = Plugin;
|
||||
$.fn.collapse.Constructor = Collapse;
|
||||
|
||||
// COLLAPSE NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.collapse.noConflict = function() {
|
||||
$.fn.collapse = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
// COLLAPSE DATA-API
|
||||
// =================
|
||||
|
||||
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function(e) {
|
||||
var $this = $(this);
|
||||
|
||||
if (!$this.attr('data-target')) e.preventDefault();
|
||||
|
||||
var $target = getTargetFromTrigger($this);
|
||||
var data = $target.data('bs.collapse');
|
||||
var option = data ? 'toggle' : $this.data();
|
||||
|
||||
Plugin.call($target, option);
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,52 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
||||
// ============================================================
|
||||
|
||||
function transitionEnd() {
|
||||
var el = document.createElement('bootstrap');
|
||||
|
||||
var transEndEventNames = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
|
||||
for (var name in transEndEventNames) {
|
||||
if (el.style[name] !== undefined) {
|
||||
return { end: transEndEventNames[name] };
|
||||
}
|
||||
}
|
||||
|
||||
return false; // explicit for ie8 ( ._.)
|
||||
}
|
||||
|
||||
// http://blog.alexmaccaw.com/css-transitions
|
||||
$.fn.emulateTransitionEnd = function(duration) {
|
||||
var called = false;
|
||||
var $el = this;
|
||||
$(this).one('bsTransitionEnd', function() { called = true; });
|
||||
var callback = function() { if (!called) $($el).trigger($.support.transition.end); };
|
||||
setTimeout(callback, duration);
|
||||
return this;
|
||||
};
|
||||
|
||||
$(function() {
|
||||
$.support.transition = transitionEnd();
|
||||
|
||||
if (!$.support.transition) return;
|
||||
|
||||
$.event.special.bsTransitionEnd = {
|
||||
bindType: $.support.transition.end,
|
||||
delegateType: $.support.transition.end,
|
||||
handle: function(e) {
|
||||
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
@@ -1,3 +1,3 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Inconsolata:400,700|&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Josefin Slab","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
|
||||
|
||||
/*# sourceMappingURL=../css-compiled/fonts.css.map */
|
||||
/*# sourceMappingURL=fonts.css.map */
|
||||
|
||||
@@ -1,11 +1 @@
|
||||
{
|
||||
"version": 3,
|
||||
"file": "../scss/fonts.css",
|
||||
"sources": [
|
||||
"../scss/fonts.scss",
|
||||
"../hdr0",
|
||||
"../scss/configuration/fonts/_support.scss"
|
||||
],
|
||||
"mappings": "AE+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF",
|
||||
"names": []
|
||||
}
|
||||
{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-header: 'Josefin Slab' !default;\n$fonts-default: 'Roboto' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n Josefin+Slab: 400,\n Roboto: '300,400,500',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\n\n\n\n\n","@function str-replace($string, $search, $replace: '') {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n@function admin-font-faces($fonts) {\n $url: \"https://fonts.googleapis.com/css?family=\";\n $nb: 0;\n\n @each $fontname, $weights in $fonts {\n\n @if $fontname == $fonts-default or\n $fontname == $fonts-header or\n $fontname == $fonts-mono {\n\n $nb: $nb + 1;\n $nb-word: 0;\n\n $fontname: str-replace(\"#{$fontname}\", \" \", \"+\");\n\n $url: $url + $fontname;\n\n @if $weights != null {\n $url: $url + \":\" + $weights;\n }\n\n @if $nb < 3 {\n $url: $url + \"|\";\n }\n }\n }\n\n @return $url + \"&subset=latin-ext\";\n}\n\n@mixin body-fonts($font) {\n body, h5, h6,\n .badge, .note, .grav-mdeditor-preview,\n input, select, textarea, button, .selectize-input {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin header-fonts($font) {\n h1, h2, h3, h4,\n .form-tabs > label, .label {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin mono-fonts($font) {\n code, kbd, pre, samp,\n body .CodeMirror {\n font-family: \"#{$font}\", \"Monaco\", \"Consolas\", \"Lucida Console\", monospace !important;\n }\n}\n$font-url: admin-font-faces($font-definitions);\n\n@import url(\"#{$font-url}\");\n\n@include body-fonts($fonts-default);\n\n@include header-fonts($fonts-header);\n\n@include mono-fonts($fonts-mono);\n\n\n\n\n\n"],"names":[],"mappings":"AC+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,UAAU,CAAG,KAAK,CAAE,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CACpB,IAAI,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
|
||||
@@ -1,3 +1,3 @@
|
||||
body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input,h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label,body .CodeMirror pre{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
|
||||
|
||||
/*# sourceMappingURL=../css-compiled/simple-fonts.css.map */
|
||||
/*# sourceMappingURL=simple-fonts.css.map */
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
{
|
||||
"version": 3,
|
||||
"file": "../scss/simple-fonts.css",
|
||||
"sources": [
|
||||
"../scss/simple-fonts.scss",
|
||||
"../hdr0"
|
||||
],
|
||||
"mappings": "AAAA,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,CACjD,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,CAC1C,AAAiB,IAAb,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAuE,CACvF,AACD,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,AAAE,CAClB,WAAW,CAAE,iDAAkD,CAClE",
|
||||
"names": []
|
||||
}
|
||||
{"version":3,"file":"simple-fonts.css","sources":["simple-fonts.scss"],"sourcesContent":["body, h5, h6,\n.badge, .note, .grav-mdeditor-preview,\ninput, select, textarea, button, .selectize-input,\nh1, h2, h3, h4,\n#admin-menu li, .form-tabs > label, .label,\nbody .CodeMirror pre {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\ncode, kbd, pre, samp {\n font-family: \"Monaco\", \"Consolas\", \"Lucida Console\", monospace;\n}\n\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,CACjD,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,WAAW,CAAC,EAAE,CAAE,UAAU,CAAG,KAAK,CAAE,MAAM,CAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAsE,CACtF,AACD,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,AAAE,CAClB,WAAW,CAAE,iDAAiD,CACjE"}
|
||||
@@ -0,0 +1,80 @@
|
||||
(function($) {
|
||||
$(function() {
|
||||
/**
|
||||
* polyfill for html5 form attr
|
||||
*/
|
||||
|
||||
// detect if browser supports this
|
||||
var sampleElement = $('[form]').get(0);
|
||||
var isIE11 = !(window.ActiveXObject) && "ActiveXObject" in window;
|
||||
if (sampleElement && window.HTMLFormElement && sampleElement.form instanceof HTMLFormElement && !isIE11) {
|
||||
// browser supports it, no need to fix
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a field to a form
|
||||
*
|
||||
*/
|
||||
$.fn.appendField = function(data) {
|
||||
// for form only
|
||||
if (!this.is('form')) return;
|
||||
|
||||
// wrap data
|
||||
if (!$.isArray(data) && data.name && data.value) {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
var $form = this;
|
||||
|
||||
// attach new params
|
||||
$.each(data, function(i, item) {
|
||||
$('<input/>')
|
||||
.attr('type', 'hidden')
|
||||
.attr('name', item.name)
|
||||
.val(item.value).appendTo($form);
|
||||
});
|
||||
|
||||
return $form;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all input fields with form attribute point to jQuery object
|
||||
*
|
||||
*/
|
||||
$('form[id]').submit(function(e) {
|
||||
// serialize data
|
||||
var data = $('[form=' + this.id + ']').serializeArray();
|
||||
// append data to form
|
||||
$(this).appendField(data);
|
||||
}).each(function() {
|
||||
var form = this,
|
||||
$fields = $('[form=' + this.id + ']');
|
||||
|
||||
$fields.filter('button, input')
|
||||
.filter('[type=reset],[type=submit],[type=image],button')
|
||||
.click(function() {
|
||||
var type = this.type.toLowerCase();
|
||||
if (type === 'reset') {
|
||||
// reset form
|
||||
form.reset();
|
||||
// for elements outside form
|
||||
$fields.each(function() {
|
||||
this.value = this.defaultValue;
|
||||
this.checked = this.defaultChecked;
|
||||
}).filter('select').each(function() {
|
||||
$(this).find('option').each(function() {
|
||||
this.selected = this.defaultSelected;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$(form).appendField({
|
||||
name: this.name,
|
||||
value: this.value
|
||||
}).submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -13,7 +13,6 @@
|
||||
"author": "RocketTheme, LLC",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.7",
|
||||
"chartist": "0.11.0",
|
||||
"codemirror": "^5.30.0",
|
||||
"cookies-js": "^1.2.3",
|
||||
@@ -22,11 +21,13 @@
|
||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
||||
"es6-map": "^0.1.5",
|
||||
"es6-promise": "^4.1.1",
|
||||
"exif-js": "^2.3.0",
|
||||
"gemini-scrollbar": "^1.5.2",
|
||||
"immutable": "^3.8.1",
|
||||
"immutablediff": "^0.4.4",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mout": "^1.0.0",
|
||||
"popper.js": "^1.14.4",
|
||||
"rangetouch": "^1.0.5",
|
||||
"remodal": "^1.1.1",
|
||||
"selectize": "^0.12.4",
|
||||
|
||||
@@ -251,7 +251,7 @@ tr {
|
||||
|
||||
&:focus, &:hover {
|
||||
color: $button-text;
|
||||
background-color: $button-bg;
|
||||
background-color: darken($button-bg, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ tr {
|
||||
}
|
||||
}
|
||||
|
||||
.instructions {
|
||||
.instructions, p {
|
||||
color: rgba($nav-text,0.8);
|
||||
}
|
||||
|
||||
@@ -306,6 +306,10 @@ tr {
|
||||
background: darken($nav-bg, 5%) url(../images/logo.svg) 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: $nav-text;
|
||||
}
|
||||
|
||||
form {
|
||||
input {
|
||||
background: lighten($nav-bg, 3%);
|
||||
@@ -674,6 +678,10 @@ form {
|
||||
input {
|
||||
color: $form-field-text;
|
||||
}
|
||||
|
||||
a.remove {
|
||||
color: $page-bg !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -909,16 +917,16 @@ form {
|
||||
}
|
||||
|
||||
.form-fieldset {
|
||||
background-color: #f7f7f7;
|
||||
border: 2px solid #e1e1e1;
|
||||
background-color: darken($content-bg, 1%);
|
||||
border: 1px solid $form-border;
|
||||
}
|
||||
|
||||
.form-fieldset--label {
|
||||
background-color: #f3f3f3;
|
||||
background-color: darken($content-bg, 4%);
|
||||
|
||||
&:hover,
|
||||
.form-fieldset input:checked + & {
|
||||
background-color: #eee;
|
||||
background-color: darken($content-bg, 6%);
|
||||
}
|
||||
}
|
||||
#admin-main {
|
||||
|
||||