diff --git a/about_amharic.html b/about_amharic.html new file mode 100644 index 0000000..1008de3 --- /dev/null +++ b/about_amharic.html @@ -0,0 +1,85 @@ + +
+ +
+ +
+ +
የቢላል ጣሃ ፕሮፋይል
+ +
+ + +
+ +

ስለ እኔ

+ + +

እኔ ከፍተኛ አፈፃፀም ያላቸውን የዌብ እና የሞባይል መተግበሪያዎችን ለመገንባት የምተጋ የሶፍትዌር መሐንዲስ ነኝ። በቴክኖሎጂ ውስጥ ያለኝ ጉዞ ነገሮች እንዴት እንደሚሠሩ በመገረም የጀመረ ሲሆን ይህም በሶፍትዌር ልማት ውስጥ ሙያ እንድከተል አድርጎኛል። ዛሬ የእውነተኛ ዓለም ችግሮችን የሚፈቱ ዲጂታል ተሞክሮዎችን በመፍጠር ላይ አተኩራለሁ።

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

ትምህርት

+ +
+ +
+

ቢ.ኤስ.ሲ. በሶፍትዌር ኢንጂነሪንግ

+

የዩኒቨርሲቲ ስም | 2020 - 2024 (የሚጠበቅ)

+

በአልጎሪዝም፣ በዳታ ስትራክቸር እና በሶፍትዌር አርክቴክቸር ላይ ያተኮረ። አውቶማቲክ የደረጃ አሰጣጥ ስርዓትን ጨምሮ በርካታ ከፍተኛ ተፅዕኖ ያላቸውን የዩኒቨርሲቲ ፕሮጀክቶች አጠናቅቄአለሁ።

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

ልምድ እና ስልጠናዎች

+ +
+ +
+

የሶፍትዌር ኢንጂነሪንግ ኢንተርን

+

ቴክ ስታርትአፕ | ሰኔ 2023 - መስከረም 2023

+

React እና Firebase በመጠቀም የእውነተኛ ጊዜ የመልዕክት ዳሽቦርድ ልማት ላይ ተሳትፌአለሁ። የዳታቤዝ ጥያቄዎችን በማመቻቸት መዘግየትን በ 20% ቀንሻለሁ።

+
+ + +
+

የሞባይል መተግበሪያ ስልጠና

+

Flutter Bootcamp | 2022

+

የመስቀለኛ መንገድ ልማት፣ የሁኔታ አስተዳደር (Provider/Riverpod) እና የኤፒአይ ውህደትን የሚሸፍን ጥልቅ የ3 ወራት ፕሮግራም።

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

የሥራ ግቦች

+

ግቤ ዋና የሶፍትዌር መሐንዲስ መሆን፣ በክፍት ምንጭ ፕሮጀክቶች ላይ አስተዋፅዖ ማድረግ እና ጀማሪ አልሚዎችን ማማከር ነው። በተለይም በአርቴፊሻል ኢንተለጀንስ እና በዌብ ልማት መገናኛ ላይ ፍላጎት አለኝ።

+
+ \ No newline at end of file diff --git a/about_content_v2.html b/about_content_v2.html new file mode 100644 index 0000000..d728162 --- /dev/null +++ b/about_content_v2.html @@ -0,0 +1,85 @@ + +
+ +
+ +
+ +
Bilal Taha Profile
+ +
+ + +
+ +

About Me

+ + +

I am a dedicated Software Engineer with a passion for building high-performance web and mobile applications. My journey in technology started with a fascination for how things work, which led me to pursue a career in software development. Today, I focus on creating seamless digital experiences that solve real-world problems.

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

Education

+ +
+ +
+

B.Sc. in Software Engineering

+

University Name | 2020 - 2024 (Expected)

+

Focused on algorithms, data structures, and software architecture. Completed several high-impact university projects including an automated grading system.

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

Experience & Trainings

+ +
+ +
+

Software Engineering Intern

+

Tech Startup | June 2023 - Sept 2023

+

Contributed to the development of a real-time messaging dashboard using React and Firebase. Optimized database queries which reduced latency by 20%.

+
+ + +
+

Mobile App Training

+

Flutter Bootcamp | 2022

+

Intensive 3-month program covering cross-platform development, state management (Provider/Riverpod), and API integration.

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

Career Goals

+

My goal is to become a Lead Software Engineer, contributing to open-source projects and mentoring aspiring developers. I am particularly interested in the intersection of AI and Web development, exploring how intelligent agents can enhance user productivity.

+
+ \ No newline at end of file diff --git a/about_oromo.html b/about_oromo.html new file mode 100644 index 0000000..547c2e2 --- /dev/null +++ b/about_oromo.html @@ -0,0 +1,85 @@ + +
+ +
+ +
+ +
Profaayila Bilaal Tahaa
+ +
+ + +
+ +

Waa'ee Koo

+ + +

Ani Injiinera Sooftiweerii appilikeeshiniiwwan weebii fi moobaayilaa dandeettii olaanaa qaban ijaaruuf carraaqu dha. Adeemsi koo teeknooloojii keessatti dinqisiifannaa akkaataa wantoonni hojjetaniin kan jalqabe yoo ta'u, kunis barumsa misooma sooftiweerii akkan hordofu na taasiseera. Har'a, muuxannoowwan dijiitaalaa rakkoolee addunyaa dhugaa hiikan uumuu irratti xiyyeeffadha.

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

Barnoota

+ +
+ +
+

B.Sc. Injiinariingii Sooftiweerii keessatti

+

Maqaa Yuunivarsiitii | 2020 - 2024 (Kan eegamu)

+

Alkooriizimii, caasaa daataa (data structures) fi arkiteekcharrii sooftiweerii irratti xiyyeeffata. Pirojektoota yuunivarsiitii dhibbaa olaanaa qaban kanneen akka sirna qabxii kennuu ofiin hojjetu dabalatee hojjedheera.

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

Muuxannoo fi Leenjiiwwan

+ +
+ +
+

Injiinera Sooftiweerii Shaakalaa

+

Tech Startup | Waxabajjii 2023 - Fulbaana 2023

+

React fi Firebase fayyadamuun misooma daashboordii ergaa yeroo dhugaa irratti hirmaadheera. Gaaffiiwwan daataabeesii fooyyessuun yeroo eegumsaa (latency) 20% hir'iseera.

+
+ + +
+

Leenjii Appii Moobaayilaa

+

Flutter Bootcamp | 2022

+

Sagantaa ji'a 3 bal'aa misooma kuroos-pilaatfoormii, bulchiinsa haalaa (Provider/Riverpod) fi walitti makinsa API hammatu.

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

Galma Hojii

+

Galmi koo Injiinera Sooftiweerii Olaanaa ta'uu, pirojektoota bifa banaatiin (open-source) hojjetaman irratti gumaachuun fi ogeessota misoomaa haaraa gorsuu dha. Keessattuu walitti hidhamiinsa AI fi misooma Weebii irratti fedhii qaba.

+
+ \ No newline at end of file diff --git a/admin_credentials.txt b/admin_credentials.txt index d888f1b..f066e5c 100644 --- a/admin_credentials.txt +++ b/admin_credentials.txt @@ -1,4 +1,4 @@ WordPress Admin Credentials: URL: http://localhost/wp-admin Username: admin -Password: c59u3v2geHuIQMRP +Password: Rj0YePZPHxMWKf1z diff --git a/assets/images/projects/ecommerce.jpg b/assets/images/projects/ecommerce.jpg new file mode 100644 index 0000000..84ab087 Binary files /dev/null and b/assets/images/projects/ecommerce.jpg differ diff --git a/assets/images/projects/tasks.jpg b/assets/images/projects/tasks.jpg new file mode 100644 index 0000000..71bafb7 Binary files /dev/null and b/assets/images/projects/tasks.jpg differ diff --git a/assets/images/projects/weather.jpg b/assets/images/projects/weather.jpg new file mode 100644 index 0000000..10542ae Binary files /dev/null and b/assets/images/projects/weather.jpg differ diff --git a/assets/pasted-20260201-115553-f0fcf40c.png b/assets/pasted-20260201-115553-f0fcf40c.png new file mode 100644 index 0000000..78cc1e4 Binary files /dev/null and b/assets/pasted-20260201-115553-f0fcf40c.png differ diff --git a/contact_amharic.html b/contact_amharic.html new file mode 100644 index 0000000..5c927f1 --- /dev/null +++ b/contact_amharic.html @@ -0,0 +1,62 @@ + +
+ +
+ +
+ +
ቢላል ጣሃ
+ +
+ + + +
+ +

ያግኙኝ

+ +

በአእምሮዎ ውስጥ ፕሮጀክት አለዎት ወይስ ስለ ሶፍትዌር ኢንጂነሪንግ ሚና መወያየት ይፈልጋሉ? ከታች ካሉት አማራጮች በአንዱ በኩል ለማነጋገር ነፃነት ይሰማዎ።

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

መልዕክት ይላኩ

+

ብዙውን ጊዜ በ24 ሰዓታት ውስጥ ምላሽ እሰጣለሁ።

+ +
+ + + +
+

የእውቂያ ዝርዝሮች

+ + +

ስልክ: 0980375465

+ + + +

ቴሌግራም: @Bilnett

+ + + +

GitHub: github.com/Bilnett

+ + + +

LinkedIn: linkedin.com/in/bilaltaha

+ +
+ +
+ \ No newline at end of file diff --git a/contact_oromo.html b/contact_oromo.html new file mode 100644 index 0000000..ea23487 --- /dev/null +++ b/contact_oromo.html @@ -0,0 +1,62 @@ + +
+ +
+ +
+ +
Bilal Taha
+ +
+ + + +
+ +

Nu Quunnamaa

+ +

Pirojektii yaada keessaa qabdu moo waa'ee gahee injiinariingii sooftiweerii mari'achuu barbaaddu? Karaalee armaan gadii keessaa tokkoon nu quunnamuuf walaba ta'aa.

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

Ergaa Ergaa

+

Yeroo baay'ee sa'aatii 24 keessatti deebii nan kenna.

+ +
+ + + +
+

Bal'ina Quunnamtii

+ + +

Bilbila: 0980375465

+ + + +

Telegram: @Bilnett

+ + + +

GitHub: github.com/Bilnett

+ + + +

LinkedIn: linkedin.com/in/bilaltaha

+ +
+ +
+ \ No newline at end of file diff --git a/home_amharic.html b/home_amharic.html new file mode 100644 index 0000000..89c5f51 --- /dev/null +++ b/home_amharic.html @@ -0,0 +1,161 @@ + +
+ +
+ +
+ +

ሰላም፣ እኔ ቢላል ጣሃ እባላለሁ

+ + +

Next.jsTypeScript፣ እና Flutter ላይ ያተኮርኩ የሶፍትዌር መሃንዲስ ነኝ። ዘመናዊ፣ ተለዋዋጭ እና ለተጠቃሚ ምቹ የሆኑ ዲጂታል ተሞክሮዎችን እገነባለሁ።

+ + + + +
+ + +
+ +
ቢላል ጣሃ
+ +
+ +
+ +
+ + + + + + + +
+

ምን እሰራለሁ?

+ +
+ +
+

የፊት-ለፊት (Frontend)

+

React፣ Next.js እና Tailwind CSS በመጠቀም ምላሽ ሰጪ እና በይነተገናኝ የተጠቃሚ በይነገጾችን መፍጠር።

+
+ + +
+

የጀርባ-አጥንት (Backend)

+

በNode.js ጠንካራ የአገልጋይ-ወገን አመክንዮዎችን እና በFlutter የተለያዩ የመሳሪያ ስርዓት የሞባይል መተግበሪያዎችን ማልማት።

+
+ + +
+

ሙሉ-ጥቅል (Full-Stack)

+

ሙሉ ለሙሉ የሚሰሩ መተግበሪያዎችን ለማቅረብ እንደ PostgreSQL እና Firebase ያሉ የውሂብ ጎታዎችን ማቀናጀት።

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

ክህሎቶቼ

+ +
+
+ +

React / Next.js

+
+
+ +

JavaScript / TS

+
+
+ +

Node.js

+
+
+ +

Python

+
+
+ + +
+
+ +

Flutter / Dart

+
+
+ +

SQL / NoSQL

+
+
+ +

UI/UX Design

+
+
+ +

Cloud / DevOps

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

አገልግሎቶቼ

+ +
+
+
    +
  • የዌብ ልማት: በዘመናዊ ማዕቀፎች የተገነቡ ብጁ ድረ-ገጾች።
  • +
  • የመተግበሪያ ልማት: ለiOS እና Android የተለያዩ የመሳሪያ ስርዓት የሞባይል መተግበሪያዎች።
  • +
  • UI/UX ዲዛይን: ሊታወቁ የሚችሉ እና የሚያምሩ የተጠቃሚ ጉዞዎችን መንደፍ።
  • +
+
+
+
    +
  • API ውህደት: እንከን የለሽ የውሂብ ፍሰት አገልግሎቶችን ማገናኘት።
  • +
  • የውሂብ ጎታ አስተዳደር: ቀልጣፋ እና ደህንነቱ የተጠበቀ የውሂብ ማከማቻ መፍትሄዎች።
  • +
  • የደመና መፍትሄዎች: AWS እና Vercel በመጠቀም ማሰማራት እና ማስፋፋት።
  • +
+
+
+ +
+ + + + + + + +
+

ፕሮጀክትዎን ለመጀመር ዝግጁ ነዎት?

+
+ +
+
+ diff --git a/home_content_v2.html b/home_content_v2.html new file mode 100644 index 0000000..d1371bd --- /dev/null +++ b/home_content_v2.html @@ -0,0 +1,167 @@ + +
+ +
+ +
+ +

Hi, I'm Bilal Taha

+ + +

Software Engineer specializing in Next.js, TypeScript, and Flutter. I build modern, scalable, and user-centric digital experiences.

+ + + + +
+ + +
+ +
Bilal Taha Profile
+ +
+ +
+ +
+ + + + + + + +
+

What I Do

+ +
+ +
+

Frontend

+

Crafting responsive and interactive user interfaces using React, Next.js, and Tailwind CSS.

+
+ + +
+

Backend

+

Developing robust server-side logic with Node.js and cross-platform mobile apps with Flutter.

+
+ + +
+

Full-Stack

+

Integrating databases like PostgreSQL and Firebase to deliver end-to-end applications.

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

My Services

+ +
+ +
+ +
    +
  • Web Development: Custom websites built with modern frameworks.
  • +
  • App Development: Cross-platform mobile apps for iOS and Android.
  • +
  • UI/UX Design: Designing intuitive and beautiful user journeys.
  • +
+ +
+ + +
+ +
    +
  • API Integration: Connecting services for seamless data flow.
  • +
  • Database Management: Efficient and secure data storage solutions.
  • +
  • Cloud Solutions: Deployment and scaling using AWS and Vercel.
  • +
+ +
+ +
+ +
+ + + + + + + +
+

Latest Insights

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

Testimonials

+ +
+ +
+

"Bilal is an exceptional developer who consistently delivers high-quality code and innovative solutions. Highly recommended!"

+

— Tech Lead, Future Systems

+
+ + +
+

"The attention to detail in the UI/UX design was outstanding. Our users love the new mobile app."

+

— Product Manager, Creative Solutions

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

Ready to start your project?

+ +
+ + + +
+ +
+ \ No newline at end of file diff --git a/home_oromo.html b/home_oromo.html new file mode 100644 index 0000000..33b2064 --- /dev/null +++ b/home_oromo.html @@ -0,0 +1,113 @@ + +
+ +
+ +
+ +

Akkam, ani Bilaal Tahaa ti

+ + +

Injinera soofitweerii Next.js, TypeScript, fi Flutter irratti xiyyeeffachuun hojjedu dha. Muuxannoowwan diijitaalaa ammayyaa, hundaaf mijatoo ta'an nan ijaara.

+ + + + +
+ + +
+ +
Bilaal Tahaa Profile
+ +
+ +
+ +
+ + + + + + + +
+

Maalan Hojjada

+ +
+ +
+

Frontend

+

React, Next.js fi Tailwind CSS fayyadamuun fuulawwan weebsaayitii socho'oo fi ija qabatan nan ijaara.

+
+ + +
+

Backend

+

Node.js fayyadamuun hojiiwwan duubaa jabaatoo fi Flutter'n appii moobaayilaa gosa hundaaf ta'u nan qopheessa.

+
+ + +
+

Full-Stack

+

Kuusaa deetaa akka PostgreSQL fi Firebase fayyadamuun appii guutuu dhiyeessuu.

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

Dandeettiiwwan Koo

+ +
+
+ +

React / Next.js

+
+
+ +

JavaScript / TS

+
+
+ +

Node.js

+
+
+ +

Python

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

Pirojektii keessan jalqabuuf qophiidhaa?

+
+ +
+
+ diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..eb05590 --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,27 @@ + 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18'; +} + +function pexels_get($url) { + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true); + return null; +} + +function download_to($srcUrl, $destPath) { + $data = file_get_contents($srcUrl); + if ($data === false) return false; + if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true); + return file_put_contents($destPath, $data) !== false; +} diff --git a/projects_amharic.html b/projects_amharic.html new file mode 100644 index 0000000..990596b --- /dev/null +++ b/projects_amharic.html @@ -0,0 +1,82 @@ + +
+ +

ፕሮጀክቶቼ

+ +

ከሙሉ የዌብ አፕሊኬሽኖች እስከ ሞባይል መተግበሪያዎች የቅርብ ጊዜ ስራዎቼ ምርጫ።

+
+ + + + + + + +
+ +
+ +
+ +
የኢ-ኮሜርስ መድረክ
+ +

የኢ-ኮሜርስ መድረክ

+

የምርት ማጣሪያ፣ የቅርጫት ተግባር እና የStripe ክፍያ ውህደት ያለው ሙሉ የመስመር ላይ መደብር።

+

ቴክኖሎጂዎች: Next.js, Stripe, Tailwind CSS, PostgreSQL

+ + + +
+ + + +
+ +
የተግባር ማስተዳደሪያ መተግበሪያ
+ +

የተግባር ማስተዳደሪያ መተግበሪያ

+

የዕለት ተዕለት ተግባራትን በእውነተኛ ጊዜ ማሻሻያ እና የቡድን ትብብር ለማስተዳደር የሚረዳ ምርታማነት መሣሪያ።

+

ቴክኖሎጂዎች: React, Firebase, Material UI

+ + + +
+ +
+ + + + + + + +
+ +
+ +
የኢትዮጵያ የአየር ሁኔታ መተግበሪያ
+ +

የኢትዮጵያ የአየር ሁኔታ መተግበሪያ

+

የOpenWeather API በመጠቀም በመላው ኢትዮጵያ ለሚገኙ ከተሞች የእውነተኛ ጊዜ የአየር ሁኔታ መረጃ ያግኙ።

+

ቴክኖሎጂዎች: Next.js, OpenWeather API, Framer Motion

+ + + +
+ +
+ +
+ \ No newline at end of file diff --git a/projects_oromo.html b/projects_oromo.html new file mode 100644 index 0000000..bb22d03 --- /dev/null +++ b/projects_oromo.html @@ -0,0 +1,82 @@ + +
+ +

Pirojektoota Koo

+ +

Filannoo hojiiwwan koo dhiheenyaa, appilikeeshiniiwwan weebii guutuu irraa kaasee hanga appilikeeshiniiwwan moobaayilaatti.

+
+ + + + + + + +
+ +
+ +
+ +
Pilaatfoormii E-Commerce
+ +

Pilaatfoormii E-Commerce

+

Kuusaa toora interneetii guutuu calalchi oomishaa, dandeettii kaartii fi walitti makinsa ክፍያ Stripe qabu.

+

Teeknooloojii: Next.js, Stripe, Tailwind CSS, PostgreSQL

+ +
+ + + +
+ +
+ + + +
+ +
Appii Bulchiinsa Hojii
+ +

Appii Bulchiinsa Hojii

+

Meeshaa oomishtummaa hojiiwwan guyyaa guyyaa odээffannoo yeroo dhugaa fi dandeettii waliin hojjechuutiin bulchuuf gargaaru.

+

Teeknooloojii: React, Firebase, Material UI

+ +
+ + + +
+ +
+ +
+ + + + + + + +
+ +
+ +
Appii Haala Qilleensa Itoophiyaa
+ +

Appii Haala Qilleensa Itoophiyaa

+

API OpenWeather fayyadamuun magaalota Itoophiyaa keessa jiraniif odээffannoo haala qilleensaa yeroo dhugaa argadhu.

+

Teeknooloojii: Next.js, OpenWeather API, Framer Motion

+ +
+ + + +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/wp-content/mu-plugins/bilal-custom-styles.php b/wp-content/mu-plugins/bilal-custom-styles.php new file mode 100644 index 0000000..fa246e2 --- /dev/null +++ b/wp-content/mu-plugins/bilal-custom-styles.php @@ -0,0 +1,256 @@ + + + + + '; +}); + +add_action('wp_footer', function() { + echo ' + + + + '; +}, 100); diff --git a/wp-content/plugins/contact-form-7/admin/admin.php b/wp-content/plugins/contact-form-7/admin/admin.php new file mode 100644 index 0000000..74162d7 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/admin.php @@ -0,0 +1,759 @@ +service_exists() ) { + $integration = add_submenu_page( 'wpcf7', + __( 'Integration with External API', 'contact-form-7' ), + __( 'Integration', 'contact-form-7' ) + . wpcf7_admin_menu_change_notice( 'wpcf7-integration' ), + 'wpcf7_manage_integration', + 'wpcf7-integration', + 'wpcf7_admin_integration_page' + ); + + add_action( 'load-' . $integration, 'wpcf7_load_integration_page', 10, 0 ); + } +} + + +function wpcf7_admin_menu_change_notice( $menu_slug = '' ) { + $counts = apply_filters( 'wpcf7_admin_menu_change_notice', + array( + 'wpcf7' => 0, + 'wpcf7-new' => 0, + 'wpcf7-integration' => 0, + ) + ); + + if ( empty( $menu_slug ) ) { + $count = absint( array_sum( $counts ) ); + } elseif ( isset( $counts[$menu_slug] ) ) { + $count = absint( $counts[$menu_slug] ); + } else { + $count = 0; + } + + if ( $count ) { + return sprintf( + ' %2$s', + $count, + esc_html( number_format_i18n( $count ) ) + ); + } + + return ''; +} + + +add_action( + 'admin_enqueue_scripts', + 'wpcf7_admin_enqueue_scripts', + 10, 1 +); + +function wpcf7_admin_enqueue_scripts( $hook_suffix ) { + if ( false === strpos( $hook_suffix, 'wpcf7' ) ) { + return; + } + + wp_enqueue_style( 'contact-form-7-admin', + wpcf7_plugin_url( 'admin/includes/css/styles.css' ), + array(), WPCF7_VERSION, 'all' + ); + + if ( wpcf7_is_rtl() ) { + wp_enqueue_style( 'contact-form-7-admin-rtl', + wpcf7_plugin_url( 'admin/includes/css/styles-rtl.css' ), + array(), WPCF7_VERSION, 'all' + ); + } + + $assets = include wpcf7_plugin_path( 'admin/includes/js/index.asset.php' ); + + $assets = wp_parse_args( $assets, array( + 'dependencies' => array(), + 'version' => WPCF7_VERSION, + ) ); + + wp_enqueue_script( 'wpcf7-admin', + wpcf7_plugin_url( 'admin/includes/js/index.js' ), + $assets['dependencies'], + $assets['version'], + array( 'in_footer' => true ) + ); + + wp_set_script_translations( 'wpcf7-admin', 'contact-form-7' ); + + $wpcf7_obj = array( + 'apiSettings' => array( + 'root' => sanitize_url( rest_url( 'contact-form-7/v1' ) ), + 'namespace' => 'contact-form-7/v1', + ), + ); + + $post = wpcf7_get_current_contact_form(); + + if ( $post ) { + $wpcf7_obj = array_merge( $wpcf7_obj, array( + 'nonce' => array( + 'save' => wp_create_nonce( + sprintf( + 'wpcf7-save-contact-form_%s', + $post->initial() ? -1 : $post->id() + ) + ), + 'copy' => wp_create_nonce( + sprintf( + 'wpcf7-copy-contact-form_%s', + $post->initial() ? -1 : $post->id() + ) + ), + 'delete' => wp_create_nonce( + sprintf( + 'wpcf7-delete-contact-form_%s', + $post->initial() ? -1 : $post->id() + ) + ), + ), + 'configValidator' => array( + 'errors' => array(), + 'docUrl' => WPCF7_ConfigValidator::get_doc_link(), + ), + ) ); + + if ( + current_user_can( 'wpcf7_edit_contact_form', $post->id() ) and + wpcf7_validate_configuration() + ) { + $config_validator = new WPCF7_ConfigValidator( $post ); + $config_validator->restore(); + + $wpcf7_obj['configValidator'] = array_merge( + $wpcf7_obj['configValidator'], + array( + 'errors' => $config_validator->collect_error_messages( + array( 'decodes_html_entities' => true ) + ), + ) + ); + } + } + + wp_add_inline_script( 'wpcf7-admin', + sprintf( + 'var wpcf7 = %s;', + wp_json_encode( $wpcf7_obj, JSON_PRETTY_PRINT ) + ), + 'before' + ); +} + + +add_filter( + 'set_screen_option_wpcf7_contact_forms_per_page', + static function ( $result, $option, $value ) { + $wpcf7_screens = array( + 'wpcf7_contact_forms_per_page', + ); + + if ( in_array( $option, $wpcf7_screens, true ) ) { + $result = $value; + } + + return $result; + }, + 10, 3 +); + + +function wpcf7_load_contact_form_admin() { + global $plugin_page; + + $action = wpcf7_current_action(); + + do_action( 'wpcf7_admin_load', + wpcf7_superglobal_get( 'page' ), + $action + ); + + if ( 'save' === $action ) { + $id = wpcf7_superglobal_post( 'post_ID', '-1' ); + + check_admin_referer( 'wpcf7-save-contact-form_' . $id ); + + if ( ! current_user_can( 'wpcf7_edit_contact_form', $id ) ) { + wp_die( + esc_html( __( 'You are not allowed to edit this item.', 'contact-form-7' ) ) + ); + } + + $contact_form = wpcf7_save_contact_form( + array_merge( + wp_unslash( $_REQUEST ), + array( + 'id' => $id, + 'title' => wpcf7_superglobal_post( 'post_title', null ), + 'locale' => wpcf7_superglobal_post( 'wpcf7-locale', null ), + 'form' => wpcf7_superglobal_post( 'wpcf7-form', '' ), + 'mail' => wpcf7_superglobal_post( 'wpcf7-mail', array() ), + 'mail_2' => wpcf7_superglobal_post( 'wpcf7-mail-2', array() ), + 'messages' => wpcf7_superglobal_post( 'wpcf7-messages', array() ), + 'additional_settings' => wpcf7_superglobal_post( 'wpcf7-additional-settings', '' ), + ) + ) + ); + + if ( $contact_form and wpcf7_validate_configuration() ) { + $config_validator = new WPCF7_ConfigValidator( $contact_form ); + $config_validator->validate(); + $config_validator->save(); + } + + $query = array( + 'post' => $contact_form ? $contact_form->id() : 0, + 'active-tab' => wpcf7_canonicalize_name( + wpcf7_superglobal_post( 'active-tab' ) + ), + ); + + if ( ! $contact_form ) { + $query['message'] = 'failed'; + } elseif ( -1 === (int) $id ) { + $query['message'] = 'created'; + } else { + $query['message'] = 'saved'; + } + + $redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) ); + wp_safe_redirect( $redirect_to ); + exit(); + } + + if ( 'copy' === $action ) { + $id = absint( $_POST['post_ID'] ?? $_REQUEST['post'] ?? '' ); + + check_admin_referer( 'wpcf7-copy-contact-form_' . $id ); + + if ( ! current_user_can( 'wpcf7_edit_contact_form', $id ) ) { + wp_die( + esc_html( __( 'You are not allowed to edit this item.', 'contact-form-7' ) ) + ); + } + + $query = array(); + + if ( $contact_form = wpcf7_contact_form( $id ) ) { + $new_contact_form = $contact_form->copy(); + $new_contact_form->save(); + + $query['post'] = $new_contact_form->id(); + $query['message'] = 'created'; + } + + $redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) ); + + wp_safe_redirect( $redirect_to ); + exit(); + } + + if ( 'delete' === $action ) { + $nonce_action = 'bulk-posts'; + + if ( + $post_id = wpcf7_superglobal_post( 'post_ID' ) or + ! is_array( $post_id = wpcf7_superglobal_request( 'post', array() ) ) + ) { + $nonce_action = sprintf( 'wpcf7-delete-contact-form_%s', $post_id ); + } + + check_admin_referer( $nonce_action ); + + $posts = array_filter( (array) $post_id ); + + $deleted = 0; + + foreach ( $posts as $post ) { + $post = WPCF7_ContactForm::get_instance( $post ); + + if ( empty( $post ) ) { + continue; + } + + if ( ! current_user_can( 'wpcf7_delete_contact_form', $post->id() ) ) { + wp_die( + esc_html( __( 'You are not allowed to delete this item.', 'contact-form-7' ) ) + ); + } + + if ( ! $post->delete() ) { + wp_die( + esc_html( __( 'Error in deleting.', 'contact-form-7' ) ) + ); + } + + $deleted += 1; + } + + $query = array(); + + if ( ! empty( $deleted ) ) { + $query['message'] = 'deleted'; + } + + $redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) ); + + wp_safe_redirect( $redirect_to ); + exit(); + } + + $post = null; + + if ( 'wpcf7-new' === $plugin_page ) { + $post = WPCF7_ContactForm::get_template( array( + 'locale' => wpcf7_superglobal_get( 'locale', null ), + ) ); + } elseif ( $post_id = wpcf7_superglobal_get( 'post' ) ) { + $post = WPCF7_ContactForm::get_instance( $post_id ); + } + + $current_screen = get_current_screen(); + + $help_tabs = new WPCF7_Help_Tabs( $current_screen ); + + if ( $post and current_user_can( 'wpcf7_edit_contact_form', $post->id() ) ) { + $help_tabs->set_help_tabs( 'edit' ); + } else { + $help_tabs->set_help_tabs( 'list' ); + + if ( ! class_exists( 'WPCF7_Contact_Form_List_Table' ) ) { + require_once WPCF7_PLUGIN_DIR . '/admin/includes/class-contact-forms-list-table.php'; + } + + add_filter( + 'manage_' . $current_screen->id . '_columns', + array( 'WPCF7_Contact_Form_List_Table', 'define_columns' ), + 10, 0 + ); + + add_screen_option( 'per_page', array( + 'default' => 20, + 'option' => 'wpcf7_contact_forms_per_page', + ) ); + } +} + + +function wpcf7_admin_management_page() { + if ( $post = wpcf7_get_current_contact_form() ) { + $post_id = $post->initial() ? -1 : $post->id(); + + require_once WPCF7_PLUGIN_DIR . '/admin/includes/editor.php'; + require_once WPCF7_PLUGIN_DIR . '/admin/edit-contact-form.php'; + return; + } + + if ( + 'validate' === wpcf7_current_action() and + wpcf7_validate_configuration() and + current_user_can( 'wpcf7_edit_contact_forms' ) + ) { + wpcf7_admin_bulk_validate_page(); + return; + } + + $list_table = new WPCF7_Contact_Form_List_Table(); + $list_table->prepare_items(); + + $formatter = new WPCF7_HTMLFormatter( array( + 'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array( + 'form' => array( + 'method' => true, + ), + ) ), + ) ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'wrap', + 'id' => 'wpcf7-contact-form-list-table', + ) ); + + $formatter->append_start_tag( 'h1', array( + 'class' => 'wp-heading-inline', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Contact Forms', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h1' ); + + if ( current_user_can( 'wpcf7_edit_contact_forms' ) ) { + $formatter->append_preformatted( + wpcf7_link( + menu_page_url( 'wpcf7-new', false ), + __( 'Add Contact Form', 'contact-form-7' ), + array( 'class' => 'page-title-action' ) + ) + ); + } + + if ( $search_keyword = wpcf7_superglobal_request( 's' ) ) { + $formatter->append_start_tag( 'span', array( + 'class' => 'subtitle', + ) ); + + $formatter->append_preformatted( + sprintf( + /* translators: %s: Search query. */ + __( 'Search results for: %s', 'contact-form-7' ), + esc_html( $search_keyword ) + ) + ); + + $formatter->end_tag( 'span' ); + } + + $formatter->append_start_tag( 'hr', array( + 'class' => 'wp-header-end', + ) ); + + $formatter->call_user_func( static function () { + do_action( 'wpcf7_admin_warnings', + 'wpcf7', wpcf7_current_action(), null + ); + + wpcf7_welcome_panel(); + + do_action( 'wpcf7_admin_notices', + 'wpcf7', wpcf7_current_action(), null + ); + } ); + + $formatter->append_start_tag( 'form', array( + 'method' => 'get', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'name' => 'page', + 'value' => wpcf7_superglobal_request( 'page' ), + ) ); + + $formatter->call_user_func( static function () use ( $list_table ) { + $list_table->search_box( + __( 'Search Contact Forms', 'contact-form-7' ), + 'wpcf7-contact' + ); + + $list_table->display(); + } ); + + $formatter->print(); +} + + +function wpcf7_admin_add_new_page() { + $post = wpcf7_get_current_contact_form(); + + if ( ! $post ) { + $post = WPCF7_ContactForm::get_template(); + } + + $post_id = -1; + + require_once WPCF7_PLUGIN_DIR . '/admin/includes/editor.php'; + require_once WPCF7_PLUGIN_DIR . '/admin/edit-contact-form.php'; +} + + +function wpcf7_load_integration_page() { + do_action( 'wpcf7_admin_load', + wpcf7_superglobal_get( 'page' ), + wpcf7_current_action() + ); + + $integration = WPCF7_Integration::get_instance(); + + if ( + $service_name = wpcf7_superglobal_request( 'service' ) and + $integration->service_exists( $service_name ) + ) { + $service = $integration->get_service( $service_name ); + $service->load( wpcf7_current_action() ); + } + + $help_tabs = new WPCF7_Help_Tabs( get_current_screen() ); + $help_tabs->set_help_tabs( 'integration' ); +} + + +function wpcf7_admin_integration_page() { + $integration = WPCF7_Integration::get_instance(); + + $service_name = wpcf7_superglobal_request( 'service' ); + $service = null; + + if ( $service_name and $integration->service_exists( $service_name ) ) { + $service = $integration->get_service( $service_name ); + } + + $formatter = new WPCF7_HTMLFormatter( array( + 'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array( + 'form' => array( + 'action' => true, + 'method' => true, + ), + ) ), + ) ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'wrap', + 'id' => 'wpcf7-integration', + ) ); + + $formatter->append_start_tag( 'h1' ); + + $formatter->append_preformatted( + esc_html( __( 'Integration with External API', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h1' ); + + $formatter->append_start_tag( 'p' ); + + $formatter->append_preformatted( + sprintf( + /* translators: %s: URL to support page about integration with external APIs */ + __( 'You can expand the possibilities of your contact forms by integrating them with external services. For details, see Integration with external APIs.', 'contact-form-7' ), + __( 'https://contactform7.com/integration-with-external-apis/', 'contact-form-7' ) + ) + ); + + $formatter->end_tag( 'p' ); + + $formatter->call_user_func( + static function () use ( $integration, $service, $service_name ) { + do_action( 'wpcf7_admin_warnings', + 'wpcf7-integration', wpcf7_current_action(), $service + ); + + do_action( 'wpcf7_admin_notices', + 'wpcf7-integration', wpcf7_current_action(), $service + ); + + if ( $service ) { + $message = wpcf7_superglobal_request( 'message' ); + $service->admin_notice( $message ); + + $integration->list_services( array( + 'include' => $service_name, + ) ); + } else { + $integration->list_services(); + } + } + ); + + $formatter->print(); +} + + +add_action( 'wpcf7_admin_notices', 'wpcf7_admin_updated_message', 10, 3 ); + +function wpcf7_admin_updated_message( $page, $action, $object ) { + if ( ! in_array( $page, array( 'wpcf7', 'wpcf7-new' ), true ) ) { + return; + } + + $message_type = wpcf7_superglobal_request( 'message' ); + + if ( ! $message_type ) { + return; + } + + $notice_type = 'success'; + + if ( 'created' === $message_type ) { + $message = __( 'Contact form created.', 'contact-form-7' ); + } elseif ( 'saved' === $message_type ) { + $message = __( 'Contact form saved.', 'contact-form-7' ); + } elseif ( 'deleted' === $message_type ) { + $message = __( 'Contact form deleted.', 'contact-form-7' ); + } elseif ( 'failed' === $message_type ) { + $notice_type = 'error'; + $message = __( 'There was an error saving the contact form.', 'contact-form-7' ); + } elseif ( 'validated' === $message_type ) { + $bulk_validate = WPCF7::get_option( 'bulk_validate', array() ); + $count_invalid = absint( $bulk_validate['count_invalid'] ?? 0 ); + + if ( $count_invalid ) { + $notice_type = 'warning'; + + $message = sprintf( + /* translators: %s: number of contact forms */ + _n( + 'Configuration validation completed. %s invalid contact form was found.', + 'Configuration validation completed. %s invalid contact forms were found.', + $count_invalid, 'contact-form-7' + ), + number_format_i18n( $count_invalid ) + ); + } else { + $message = __( 'Configuration validation completed. No invalid contact form was found.', 'contact-form-7' ); + } + } + + if ( ! empty( $message ) ) { + wp_admin_notice( + $message, + array( 'type' => $notice_type ) + ); + } +} + + +add_filter( 'plugin_action_links', 'wpcf7_plugin_action_links', 10, 2 ); + +function wpcf7_plugin_action_links( $links, $file ) { + if ( WPCF7_PLUGIN_BASENAME !== $file ) { + return $links; + } + + if ( ! current_user_can( 'wpcf7_read_contact_forms' ) ) { + return $links; + } + + $settings_link = wpcf7_link( + menu_page_url( 'wpcf7', false ), + __( 'Settings', 'contact-form-7' ) + ); + + array_unshift( $links, $settings_link ); + + return $links; +} + + +add_action( 'wpcf7_admin_warnings', 'wpcf7_old_wp_version_error', 10, 3 ); + +function wpcf7_old_wp_version_error( $page, $action, $object ) { + $wp_version = get_bloginfo( 'version' ); + + if ( version_compare( $wp_version, WPCF7_REQUIRED_WP_VERSION, '<' ) ) { + wp_admin_notice( + sprintf( + /* translators: 1: version of Contact Form 7, 2: version of WordPress, 3: URL */ + __( 'Contact Form 7 %1$s requires WordPress %2$s or higher. Please update WordPress first.', 'contact-form-7' ), + WPCF7_VERSION, + WPCF7_REQUIRED_WP_VERSION, + admin_url( 'update-core.php' ) + ), + array( 'type' => 'warning' ) + ); + } +} + + +add_action( 'wpcf7_admin_warnings', 'wpcf7_not_allowed_to_edit', 10, 3 ); + +function wpcf7_not_allowed_to_edit( $page, $action, $object ) { + if ( $object instanceof WPCF7_ContactForm ) { + $contact_form = $object; + } else { + return; + } + + if ( ! current_user_can( 'wpcf7_edit_contact_form', $contact_form->id() ) ) { + wp_admin_notice( + __( 'You are not allowed to edit this contact form.', 'contact-form-7' ), + array( 'type' => 'warning' ) + ); + } +} + + +add_action( 'wpcf7_admin_warnings', 'wpcf7_ctct_deprecated_warning', 10, 3 ); + +function wpcf7_ctct_deprecated_warning( $page, $action, $object ) { + $service = WPCF7_ConstantContact::get_instance(); + + if ( $service->is_active() ) { + wp_admin_notice( + __( 'Contact Form 7 has completed the removal of the Constant Contact integration. We recommend Brevo as an alternative.', 'contact-form-7' ), + array( 'type' => 'warning' ) + ); + } +} + + +add_action( 'wpcf7_admin_warnings', 'wpcf7_captcha_future_warning', 10, 3 ); + +function wpcf7_captcha_future_warning( $page, $action, $object ) { + $service = WPCF7_RECAPTCHA::get_instance(); + + if ( $service->is_active() ) { + wp_admin_notice( + __( 'Attention reCAPTCHA users: Google attempts to make all reCAPTCHA users migrate to reCAPTCHA Enterprise, meaning Google charges you for API calls exceeding the free tier. Contact Form 7 supports Cloudflare Turnstile, and we recommend it unless you have reasons to use reCAPTCHA.', 'contact-form-7' ), + array( 'type' => 'warning' ) + ); + } +} diff --git a/wp-content/plugins/contact-form-7/admin/edit-contact-form.php b/wp-content/plugins/contact-form-7/admin/edit-contact-form.php new file mode 100644 index 0000000..e63c20b --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/edit-contact-form.php @@ -0,0 +1,484 @@ +', + wpcf7_format_atts( array( + 'type' => 'submit', + 'class' => 'button-primary', + 'name' => 'wpcf7-save', + 'value' => __( 'Save', 'contact-form-7' ), + ) ) +); + +$formatter = new WPCF7_HTMLFormatter( array( + 'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array( + 'form' => array( + 'method' => true, + 'action' => true, + 'id' => true, + 'class' => true, + 'disabled' => true, + ), + ) ), +) ); + +$formatter->append_start_tag( 'div', array( + 'class' => 'wrap', + 'id' => 'wpcf7-contact-form-editor', +) ); + +$formatter->append_start_tag( 'h1', array( + 'class' => 'wp-heading-inline', +) ); + +$formatter->append_preformatted( + esc_html( $post->initial() + ? __( 'Add Contact Form', 'contact-form-7' ) + : __( 'Edit Contact Form', 'contact-form-7' ) + ) +); + +$formatter->end_tag( 'h1' ); + +if ( ! $post->initial() and current_user_can( 'wpcf7_edit_contact_forms' ) ) { + $formatter->append_whitespace(); + + $formatter->append_preformatted( + wpcf7_link( + menu_page_url( 'wpcf7-new', false ), + __( 'Add Contact Form', 'contact-form-7' ), + array( 'class' => 'page-title-action' ) + ) + ); +} + +$formatter->append_start_tag( 'hr', array( + 'class' => 'wp-header-end', +) ); + +$formatter->call_user_func( static function () use ( $post ) { + do_action( 'wpcf7_admin_warnings', + $post->initial() ? 'wpcf7-new' : 'wpcf7', + wpcf7_current_action(), + $post + ); + + do_action( 'wpcf7_admin_notices', + $post->initial() ? 'wpcf7-new' : 'wpcf7', + wpcf7_current_action(), + $post + ); +} ); + +if ( $post ) { + $formatter->append_start_tag( 'form', array( + 'method' => 'post', + 'action' => esc_url( add_query_arg( + array( 'post' => $post_id ), + menu_page_url( 'wpcf7', false ) + ) ), + 'id' => 'wpcf7-admin-form-element', + 'disabled' => ! current_user_can( 'wpcf7_edit_contact_form', $post_id ), + ) ); + + if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { + $formatter->call_user_func( static function () use ( $post_id ) { + wp_nonce_field( 'wpcf7-save-contact-form_' . $post_id ); + } ); + } + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'id' => 'post_ID', + 'name' => 'post_ID', + 'value' => (int) $post_id, + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'id' => 'wpcf7-locale', + 'name' => 'wpcf7-locale', + 'value' => $post->locale(), + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'id' => 'hiddenaction', + 'name' => 'action', + 'value' => 'save', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'id' => 'active-tab', + 'name' => 'active-tab', + 'value' => wpcf7_superglobal_get( 'active-tab' ), + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'poststuff', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'post-body', + 'class' => 'metabox-holder columns-2 wp-clearfix', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'post-body-content', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'titlediv', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'titlewrap', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'name' => 'post_title', + 'value' => $post->initial() ? '' : $post->title(), + 'id' => 'title', + 'spellcheck' => 'true', + 'autocomplete' => 'off', + 'disabled' => ! current_user_can( 'wpcf7_edit_contact_form', $post_id ), + 'placeholder' => __( 'Enter title here', 'contact-form-7' ), + 'aria-label' => __( 'Enter title here', 'contact-form-7' ), + ) ); + + $formatter->end_tag( 'div' ); // #titlewrap + + $formatter->append_start_tag( 'div', array( + 'class' => 'inside', + ) ); + + if ( ! $post->initial() ) { + if ( $shortcode = $post->shortcode() ) { + $formatter->append_start_tag( 'p', array( + 'class' => 'description', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => 'wpcf7-shortcode', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Copy this shortcode and paste it into your post, page, or text widget content:', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'label' ); + + $formatter->append_whitespace(); + + $formatter->append_start_tag( 'span', array( + 'class' => 'shortcode wp-ui-highlight', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => 'wpcf7-shortcode', + 'readonly' => true, + 'class' => 'large-text code selectable', + 'value' => $shortcode, + ) ); + + $formatter->end_tag( 'p' ); + } + + if ( $shortcode = $post->shortcode( array( 'use_old_format' => true ) ) ) { + $formatter->append_start_tag( 'p', array( + 'class' => 'description', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => 'wpcf7-shortcode-old', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'You can also use this old-style shortcode:', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'label' ); + + $formatter->append_whitespace(); + + $formatter->append_start_tag( 'span', array( + 'class' => 'shortcode old', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => 'wpcf7-shortcode-old', + 'readonly' => true, + 'class' => 'large-text code selectable', + 'value' => $shortcode, + ) ); + + $formatter->end_tag( 'p' ); + } + } + + $formatter->end_tag( 'div' ); // .inside + $formatter->end_tag( 'div' ); // #titlediv + $formatter->end_tag( 'div' ); // #post-body-content + + $formatter->append_start_tag( 'div', array( + 'id' => 'postbox-container-1', + 'class' => 'postbox-container', + ) ); + + if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { + $formatter->append_start_tag( 'section', array( + 'id' => 'submitdiv', + 'class' => 'postbox', + ) ); + + $formatter->append_start_tag( 'h2' ); + + $formatter->append_preformatted( + esc_html( __( 'Status', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h2' ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'inside', + ) ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'submitbox', + 'id' => 'submitpost', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'minor-publishing-actions', + ) ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'hidden', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'submit', + 'class' => 'button-primary', + 'name' => 'wpcf7-save', + 'value' => __( 'Save', 'contact-form-7' ), + ) ); + + $formatter->end_tag( 'div' ); // .hidden + + if ( ! $post->initial() ) { + $formatter->append_start_tag( 'input', array( + 'type' => 'submit', + 'name' => 'wpcf7-copy', + 'class' => 'copy button', + 'value' => __( 'Duplicate', 'contact-form-7' ), + ) ); + } + + $formatter->end_tag( 'div' ); // #minor-publishing-actions + + $formatter->append_start_tag( 'div', array( + 'id' => 'misc-publishing-actions', + ) ); + + $formatter->call_user_func( static function () use ( $post_id ) { + do_action( 'wpcf7_admin_misc_pub_section', $post_id ); + } ); + + $formatter->end_tag( 'div' ); // #misc-publishing-actions + + $formatter->append_start_tag( 'div', array( + 'id' => 'major-publishing-actions', + ) ); + + if ( ! $post->initial() ) { + $formatter->append_start_tag( 'div', array( + 'id' => 'delete-action', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'submit', + 'name' => 'wpcf7-delete', + 'class' => 'delete submitdelete', + 'value' => __( 'Delete', 'contact-form-7' ), + ) ); + + $formatter->end_tag( 'div' ); // #delete-action + } + + $formatter->append_start_tag( 'div', array( + 'id' => 'publishing-action', + ) ); + + $formatter->append_preformatted( '' ); + $formatter->append_preformatted( $save_button ); + + $formatter->end_tag( 'div' ); // #publishing-action + + $formatter->append_preformatted( '
' ); + + $formatter->end_tag( 'div' ); // #major-publishing-actions + $formatter->end_tag( 'div' ); // #submitpost + $formatter->end_tag( 'div' ); // .inside + $formatter->end_tag( 'section' ); // #submitdiv + } + + $formatter->append_start_tag( 'section', array( + 'id' => 'informationdiv', + 'class' => 'postbox', + ) ); + + $formatter->append_start_tag( 'h2' ); + + $formatter->append_preformatted( + esc_html( __( 'Do you need help?', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h2' ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'inside', + ) ); + + $formatter->append_start_tag( 'p' ); + + $formatter->append_preformatted( + esc_html( __( 'Here are some available options to help solve your problems.', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'p' ); + + $formatter->append_start_tag( 'ol' ); + + $formatter->append_start_tag( 'li' ); + + $formatter->append_preformatted( + sprintf( + /* translators: 1: URL to FAQ, 2: URL to docs */ + 'FAQ and docs', + __( 'https://contactform7.com/faq/', 'contact-form-7' ), + __( 'https://contactform7.com/docs/', 'contact-form-7' ) + ) + ); + + $formatter->append_start_tag( 'li' ); + + $formatter->append_preformatted( + wpcf7_link( + __( 'https://wordpress.org/support/plugin/contact-form-7/', 'contact-form-7' ), + __( 'Support forums', 'contact-form-7' ) + ) + ); + + $formatter->append_start_tag( 'li' ); + + $formatter->append_preformatted( + wpcf7_link( + __( 'https://contactform7.com/custom-development/', 'contact-form-7' ), + __( 'Professional services', 'contact-form-7' ) + ) + ); + + $formatter->end_tag( 'ol' ); + $formatter->end_tag( 'div' ); // .inside + $formatter->end_tag( 'section' ); // #informationdiv + $formatter->end_tag( 'div' ); // #postbox-container-1 + + $formatter->append_start_tag( 'div', array( + 'id' => 'postbox-container-2', + 'class' => 'postbox-container', + ) ); + + $formatter->append_start_tag( 'div', array( + 'id' => 'contact-form-editor', + ) ); + + $formatter->call_user_func( static function () use ( $post, $post_id ) { + $editor = new WPCF7_Editor( $post ); + $panels = array(); + + if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { + $panels = array( + 'form-panel' => array( + 'title' => __( 'Form', 'contact-form-7' ), + 'callback' => 'wpcf7_editor_panel_form', + ), + 'mail-panel' => array( + 'title' => __( 'Mail', 'contact-form-7' ), + 'callback' => 'wpcf7_editor_panel_mail', + ), + 'messages-panel' => array( + 'title' => __( 'Messages', 'contact-form-7' ), + 'callback' => 'wpcf7_editor_panel_messages', + ), + ); + + $additional_settings = $post->prop( 'additional_settings' ); + + if ( ! is_scalar( $additional_settings ) ) { + $additional_settings = ''; + } + + $additional_settings = trim( $additional_settings ); + $additional_settings = explode( "\n", $additional_settings ); + $additional_settings = array_filter( $additional_settings ); + $additional_settings = count( $additional_settings ); + + $panels['additional-settings-panel'] = array( + 'title' => $additional_settings + ? sprintf( + /* translators: %d: number of additional settings */ + __( 'Additional Settings (%d)', 'contact-form-7' ), + $additional_settings + ) + : __( 'Additional Settings', 'contact-form-7' ), + 'callback' => 'wpcf7_editor_panel_additional_settings', + ); + } + + $panels = apply_filters( 'wpcf7_editor_panels', $panels ); + + foreach ( $panels as $id => $panel ) { + $editor->add_panel( $id, $panel['title'], $panel['callback'] ); + } + + $editor->display(); + } ); + + $formatter->end_tag( 'div' ); // #contact-form-editor + + if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { + $formatter->append_start_tag( 'p', array( + 'class' => 'submit', + ) ); + + $formatter->append_preformatted( $save_button ); + + $formatter->end_tag( 'p' ); + } + + $formatter->end_tag( 'div' ); // #postbox-container-2 + $formatter->end_tag( 'div' ); // #post-body + + $formatter->append_preformatted( '
' ); + + $formatter->end_tag( 'div' ); // #poststuff + $formatter->end_tag( 'form' ); +} + +$formatter->end_tag( 'div' ); // .wrap + +$formatter->print(); + +$tag_generator = WPCF7_TagGenerator::get_instance(); +$tag_generator->print_panels( $post ); + +do_action( 'wpcf7_admin_footer', $post ); diff --git a/wp-content/plugins/contact-form-7/admin/includes/admin-functions.php b/wp-content/plugins/contact-form-7/admin/includes/admin-functions.php new file mode 100644 index 0000000..11a5547 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/admin-functions.php @@ -0,0 +1,22 @@ +add( $name, $title, $callback, $options ); +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/class-contact-forms-list-table.php b/wp-content/plugins/contact-form-7/admin/includes/class-contact-forms-list-table.php new file mode 100644 index 0000000..6235966 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/class-contact-forms-list-table.php @@ -0,0 +1,241 @@ + '', + 'title' => __( 'Title', 'contact-form-7' ), + 'shortcode' => __( 'Shortcode', 'contact-form-7' ), + 'author' => __( 'Author', 'contact-form-7' ), + 'date' => __( 'Date', 'contact-form-7' ), + ); + + return $columns; + } + + public function __construct() { + parent::__construct( array( + 'singular' => 'post', + 'plural' => 'posts', + 'ajax' => false, + ) ); + } + + public function prepare_items() { + $current_screen = get_current_screen(); + $per_page = $this->get_items_per_page( 'wpcf7_contact_forms_per_page' ); + + $args = array( + 'posts_per_page' => $per_page, + 'orderby' => 'title', + 'order' => 'ASC', + 'offset' => ( $this->get_pagenum() - 1 ) * $per_page, + ); + + if ( $search_keyword = wpcf7_superglobal_request( 's' ) ) { + $args['s'] = $search_keyword; + } + + if ( $order_by = wpcf7_superglobal_request( 'orderby' ) ) { + $args['orderby'] = $order_by; + } + + if ( + $order = wpcf7_superglobal_request( 'order' ) and + 'desc' === strtolower( $order ) + ) { + $args['order'] = 'DESC'; + } + + $this->items = WPCF7_ContactForm::find( $args ); + + $total_items = WPCF7_ContactForm::count(); + $total_pages = ceil( $total_items / $per_page ); + + $this->set_pagination_args( array( + 'total_items' => $total_items, + 'total_pages' => $total_pages, + 'per_page' => $per_page, + ) ); + } + + public function get_columns() { + return get_column_headers( get_current_screen() ); + } + + protected function get_sortable_columns() { + $columns = array( + 'title' => array( 'title', true ), + 'author' => array( 'author', false ), + 'date' => array( 'date', false ), + ); + + return $columns; + } + + protected function get_bulk_actions() { + $actions = array( + 'delete' => __( 'Delete', 'contact-form-7' ), + ); + + return $actions; + } + + protected function column_default( $item, $column_name ) { + return ''; + } + + public function column_cb( $item ) { + return sprintf( + '', + $this->_args['singular'], + $item->id() + ); + } + + public function column_title( $item ) { + $edit_link = add_query_arg( + array( + 'post' => absint( $item->id() ), + 'action' => 'edit', + ), + menu_page_url( 'wpcf7', false ) + ); + + $output = sprintf( + '%3$s', + esc_url( $edit_link ), + esc_attr( sprintf( + /* translators: %s: title of contact form */ + __( 'Edit “%s”', 'contact-form-7' ), + $item->title() + ) ), + esc_html( $item->title() ) + ); + + $output = sprintf( '%s', $output ); + + if ( wpcf7_validate_configuration() + and current_user_can( 'wpcf7_edit_contact_form', $item->id() ) ) { + $config_validator = new WPCF7_ConfigValidator( $item ); + $config_validator->restore(); + + if ( $count_errors = $config_validator->count_errors() ) { + $error_notice = sprintf( + /* translators: %s: number of errors detected */ + _n( + '%s configuration error detected', + '%s configuration errors detected', + $count_errors, 'contact-form-7' ), + number_format_i18n( $count_errors ) + ); + + $output .= sprintf( + '
%s
', + $error_notice + ); + } + } + + return $output; + } + + protected function handle_row_actions( $item, $column_name, $primary ) { + if ( $column_name !== $primary ) { + return ''; + } + + $edit_link = add_query_arg( + array( + 'post' => absint( $item->id() ), + 'action' => 'edit', + ), + menu_page_url( 'wpcf7', false ) + ); + + $actions = array( + 'edit' => wpcf7_link( $edit_link, __( 'Edit', 'contact-form-7' ) ), + ); + + if ( current_user_can( 'wpcf7_edit_contact_form', $item->id() ) ) { + $copy_link = add_query_arg( + array( + 'post' => absint( $item->id() ), + 'action' => 'copy', + ), + menu_page_url( 'wpcf7', false ) + ); + + $copy_link = wp_nonce_url( + $copy_link, + 'wpcf7-copy-contact-form_' . absint( $item->id() ) + ); + + $actions = array_merge( $actions, array( + 'copy' => wpcf7_link( $copy_link, __( 'Duplicate', 'contact-form-7' ) ), + ) ); + } + + return $this->row_actions( $actions ); + } + + public function column_author( $item ) { + $post = get_post( $item->id() ); + + if ( ! $post ) { + return; + } + + $author = get_userdata( $post->post_author ); + + if ( false === $author ) { + return; + } + + return esc_html( $author->display_name ); + } + + public function column_shortcode( $item ) { + $shortcodes = array( $item->shortcode() ); + + $output = ''; + + foreach ( $shortcodes as $shortcode ) { + $output .= "\n" . sprintf( + '', + wpcf7_format_atts( array( + 'type' => 'text', + 'readonly' => true, + 'value' => $shortcode, + 'class' => 'large-text code selectable', + ) ) + ); + } + + return trim( $output ); + } + + public function column_date( $item ) { + $datetime = get_post_datetime( $item->id() ); + + if ( false === $datetime ) { + return ''; + } + + $t_time = sprintf( + /* translators: 1: date, 2: time */ + __( '%1$s at %2$s', 'contact-form-7' ), + /* translators: date format, see https://www.php.net/date */ + $datetime->format( __( 'Y/m/d', 'contact-form-7' ) ), + /* translators: time format, see https://www.php.net/date */ + $datetime->format( __( 'g:i a', 'contact-form-7' ) ) + ); + + return $t_time; + } +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/config-validator.php b/wp-content/plugins/contact-form-7/admin/includes/config-validator.php new file mode 100644 index 0000000..e727a90 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/config-validator.php @@ -0,0 +1,180 @@ + 'validate' ), + menu_page_url( 'wpcf7', false ) + ), + __( 'Validate Contact Form 7 Configuration', 'contact-form-7' ) + ) + ), + array( 'type' => 'warning' ) + ); +} + +add_action( 'wpcf7_admin_load', 'wpcf7_load_bulk_validate_page', 10, 2 ); + +function wpcf7_load_bulk_validate_page( $page, $action ) { + if ( + 'wpcf7' !== $page or + 'validate' !== $action or + ! wpcf7_validate_configuration() or + 'POST' !== wpcf7_superglobal_server( 'REQUEST_METHOD' ) + ) { + return; + } + + check_admin_referer( 'wpcf7-bulk-validate' ); + + if ( ! current_user_can( 'wpcf7_edit_contact_forms' ) ) { + wp_die( wp_kses_data( __( 'You are not allowed to validate configuration.', 'contact-form-7' ) ) ); + } + + $contact_forms = WPCF7_ContactForm::find(); + + $result = array( + 'timestamp' => time(), + 'version' => WPCF7_VERSION, + 'count_valid' => 0, + 'count_invalid' => 0, + ); + + foreach ( $contact_forms as $contact_form ) { + $config_validator = new WPCF7_ConfigValidator( $contact_form ); + $config_validator->validate(); + $config_validator->save(); + + if ( $config_validator->is_valid() ) { + $result['count_valid'] += 1; + } else { + $result['count_invalid'] += 1; + } + } + + WPCF7::update_option( 'bulk_validate', $result ); + + $redirect_to = add_query_arg( + array( + 'message' => 'validated', + ), + menu_page_url( 'wpcf7', false ) + ); + + wp_safe_redirect( $redirect_to ); + exit(); +} + +function wpcf7_admin_bulk_validate_page() { + $contact_forms = WPCF7_ContactForm::find(); + $count = WPCF7_ContactForm::count(); + + $submit_text = sprintf( + /* translators: %s: number of contact forms */ + _n( + 'Validate %s contact form now', + 'Validate %s contact forms now', + $count, 'contact-form-7' + ), + number_format_i18n( $count ) + ); + + $formatter = new WPCF7_HTMLFormatter( array( + 'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array( + 'form' => array( + 'action' => true, + 'method' => true, + ), + ) ), + ) ); + + $formatter->append_start_tag( 'div', array( + 'class' => 'wrap', + ) ); + + $formatter->append_start_tag( 'h1' ); + + $formatter->append_preformatted( + esc_html( __( 'Validate Configuration', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h1' ); + + $formatter->append_start_tag( 'form', array( + 'method' => 'post', + 'action' => '', + ) ); + + $formatter->append_start_tag( 'p' ); + + $formatter->call_user_func( static function () { + wp_nonce_field( 'wpcf7-bulk-validate' ); + } ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'name' => 'action', + 'value' => 'validate', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'submit', + 'class' => 'button', + 'value' => $submit_text, + ) ); + + $formatter->end_tag( 'form' ); + + $formatter->append_preformatted( + wpcf7_link( + __( 'https://contactform7.com/configuration-validator-faq/', 'contact-form-7' ), + __( 'FAQ about Configuration Validator', 'contact-form-7' ) + ) + ); + + $formatter->print(); +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/css/styles-rtl.css b/wp-content/plugins/contact-form-7/admin/includes/css/styles-rtl.css new file mode 100644 index 0000000..4b5de0f --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/css/styles-rtl.css @@ -0,0 +1,77 @@ +/* + * Form Tab + */ +.tag-generator-panel { + text-align: right; +} + +.tag-generator-dialog > .close-button { + right: auto; + left: 8px; +} + +form.tag-generator-panel[data-version="1"] { + .control-box > fieldset > legend { + border: 1px solid #dfdfdf; + border-right: 4px solid #00a0d2; + } + + .insert-box input.tag { + float: right; + } + + .insert-box .submitbox input[type="button"] { + float: left; + } +} + +/* + * Mail Tab + */ +.contact-form-editor-box-mail span.mailtag { + margin: 0 4px 0 0; +} + +/* + * Welcome Panel + */ +.wpcf7-welcome-panel .welcome-panel-close { + left: 10px; + right: auto; + padding: 10px 21px 10px 15px; +} + +.wpcf7-welcome-panel .welcome-panel-close::before { + right: 0; + left: auto; +} + +.wpcf7-welcome-panel .welcome-panel-content { + margin-right: 13px; +} + +.wpcf7-welcome-panel .welcome-panel-column { + float: right; + padding: 0 0 0 2%; +} + +/* + * Integration + */ +.card { + border-left: 1px solid #e5e5e5; + border-right: 4px solid #e5e5e5; +} + +.card img.icon { + float: right; + margin: 8px -8px 8px 8px; +} + +.card h2.title { + float: right; +} + +.card .infobox { + float: left; +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/css/styles.css b/wp-content/plugins/contact-form-7/admin/includes/css/styles.css new file mode 100644 index 0000000..0aa85ab --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/css/styles.css @@ -0,0 +1,614 @@ +#titlediv .inside p.description { + margin: 8px 2px 0; +} + +#titlediv .inside p.description label { + cursor: pointer; +} + +span.shortcode { + display: block; + margin: 2px 0; +} + +span.shortcode.old { + background: #777; + color: #fff; +} + +span.shortcode input { + font-size: 12px; + border: none; + box-shadow: none; + padding: 4px 8px; + margin: 0; +} + +#wpcf7-contact-form-list-table span.shortcode input, +#wpcf7-contact-form-editor span.shortcode input { + background: transparent; +} + +#wpcf7-contact-form-list-table span.shortcode input { + color: #444; +} + +#wpcf7-contact-form-editor span.shortcode input { + color: #fff; + width: 100%; +} + +#submitpost input.copy { + margin-bottom: 10px; +} + +#submitpost input.delete { + padding: 0; + margin: 0; + border: none; + cursor: pointer; + background: inherit; + color: #a00; +} + +#submitpost input.delete:hover { + color: #dc3232; /* Red */ +} + +#submitpost input.delete:focus { + outline: thin dotted; +} + +.postbox-container .postbox h3 { + border-bottom: 1px solid transparent; +} + +div.config-error, span.config-error, ul.config-error { + color: #444; + font-style: normal; + font-size: 13px; +} + +ul.config-error { + margin: 2px 0; +} + +ul.config-error li { + list-style: none; + padding: 2px 2px; + margin: 0; +} + +#misc-publishing-actions .config-error { + line-height: 2; +} + +[data-config-field][aria-invalid="true"] { + border-color: #dc3232; +} + +#contact-form-editor-tabs .icon-in-circle, +#contact-form-editor .config-error .icon-in-circle, +.wp-list-table .config-error .icon-in-circle, +.icon-in-circle { + display: inline-block; + vertical-align: baseline; + margin: 1px 6px 0; + padding: 0 5px; + min-width: 7px; + height: 17px; + border-radius: 11px; + background-color: #ca4a1f; + color: #fff; + font-size: 12px; + font-weight: bold; + line-height: 17px; + text-align: center; + z-index: 26; +} + +/* + * Tabs + */ +#contact-form-editor-tabs { + border-bottom: 1px solid #aaa; + padding: 9px 10px 0; + margin: 0; +} + +#contact-form-editor-tabs button { + display: inline-block; + border: 1px solid #ccc; + border-bottom: 1px solid #aaa; + padding: 6px 10px; + margin: 0 4px -1px; + color: #333; + background-color: #e4e4e4; + font-size: 14px; + font-weight: normal; + line-height: 20px; +} + +#contact-form-editor-tabs button[aria-selected="true"] { + border-top: 1px solid #aaa; + border-right: 1px solid #aaa; + border-left: 1px solid #aaa; + border-bottom: 1px solid #f5f5f5; + color: #000; + background-color: #f5f5f5; + font-weight: bold; +} + +#contact-form-editor-tabs button:hover { + color: #000; +} + +#contact-form-editor .contact-form-editor-panel > div.config-error { + margin-bottom: 1.4em; +} + +#contact-form-editor-tabs button[aria-selected="true"] .icon-in-circle { + display: none; +} + +#contact-form-editor .contact-form-editor-panel h2 { + font-size: 18px; + font-weight: 400; + line-height: 24px; + margin: 8px 0; + padding: 0; +} + +#contact-form-editor .contact-form-editor-panel { + background-color: #f5f5f5; + border: 1px solid #aaa; + border-top: none; + padding: 16px; +} + +#contact-form-editor .form-table th { + width: 100px; +} + +#contact-form-editor .contact-form-editor-panel fieldset legend { + line-height: 1.5; + margin: .6em 0 .4em; +} + +/* + * Form Tab + */ +#tag-generator-list button { + font-size: 12px; + height: 26px; + line-height: 24px; + margin: 2px; + padding: 0 8px 1px; +} + +.tag-generator-dialog { + padding: 12px; + border: 1px solid #c3c4c7; + box-shadow: 0 1px 1px rgba( 0, 0, 0, 0.04 ); + height: 720px; + width: 720px; + overflow: auto; +} + +.tag-generator-dialog::backdrop { + background: rgb( 0 0 0 / 50% ); +} + +.tag-generator-dialog > .close-button { + position: absolute; + top: 8px; + right: 8px; +} + +form.tag-generator-panel { + display: flex; + flex-direction: column; + height: 720px; + width: 720px; + max-height: 100%; + max-width: 100%; + margin-block-end: 0; +} + +form.tag-generator-panel[data-version="2"] { + &:invalid .mail-tag-tip { + display: none; + } + + .description-box { + box-sizing: border-box; + flex: none; + margin-inline: -12px; + padding-inline: 12px; + border-bottom: 1px solid #dfdfdf; + } + + .control-box { + box-sizing: border-box; + flex: auto; + display: flex; + flex-direction: column; + gap: 12px 0; + min-height: 120px; + overflow: auto; + } + + .control-box > fieldset { + margin-block: 8px; + margin-inline-start: 10em; + line-height: 2.25; + } + + .control-box > fieldset > legend { + font-weight: bolder; + margin-block: 8px; + margin-inline-start: -10em; + line-height: 1.25; + } + + .control-box input[type="text"] { + width: 20rem; + max-width: 88%; + } + + .control-box input[type="number"] { + width: 8rem; + max-width: 44%; + } + + .control-box textarea { + height: 12ex; + width: 20rem; + max-width: 88%; + } + + .control-box select { + width: 20rem; + max-width: 88%; + } + + .control-box input:invalid, + .control-box textarea:invalid { + background-color: #ffe9de; + } + + .insert-box { + box-sizing: border-box; + flex: none; + margin-block-end: -12px; + margin-inline: -12px; + padding-block: 24px 12px; + padding-inline: 12px; + background-color: #fcfcfc; + border-top: 1px solid #dfdfdf; + } + + .insert-box .flex-container { + display: flex; + } + + .insert-box .flex-container > [data-tag-part="tag"] { + flex: auto; + margin-inline-end: 8px; + } + + .insert-box .mail-tag-tip::before { + font: 1.2rem dashicons; + content: "\f348" / ''; + color: #646970; + padding-inline-end: 4px; + vertical-align: top; + } +} + +form.tag-generator-panel[data-version="1"] { + .control-box { + padding: 0; + margin: 0; + overflow: auto; + flex-grow: 1; + } + + .control-box > fieldset > legend { + border: 1px solid #dfdfdf; + border-left: 4px solid #00a0d2; + background: #f7fcfe; + padding: 4px 12px; + margin: 4px 0; + line-height: 1.4em; + width: 100%; + box-sizing: border-box; + } + + table { + width: 100%; + } + + table.form-table th { + width: 120px; + padding: 4px 10px 4px 0; + font-size: 13px; + } + + table.form-table td { + padding: 4px 10px; + font-size: 13px; + } + + .control-box input.oneline { + width: 200px; + } + + .control-box input.large-text { + width: 400px; + } + + .control-box textarea.values { + width: 200px; + height: 6em; + } + + .control-box input[type="number"], + .control-box input[type="date"] { + width: 88px; + } + + .control-box table caption { + text-align: left; + font-size: 110%; + font-weight: bold; + color: #777; + margin: 10px 0 5px; + } + + .control-box table.form-table td label { + line-height: 1.1em; + } + + .control-box table.form-table td label .description { + line-height: 1.4em; + } + + .insert-box { + margin: 0 -15px -15px; + padding: 8px 16px; + background-color: #fcfcfc; + border-top: 1px solid #dfdfdf; + overflow: auto; + } + + .insert-box input.tag { + width: 510px; + float: left; + background-color: transparent; + box-shadow: none; + } + + .insert-box .submitbox { + padding: 0; + } + + .insert-box .submitbox input[type="button"] { + float: right; + } + + .insert-box .description label { + cursor: text; + } +} + +/* + * Mail Tab + */ +.contact-form-editor-box-mail span.mailtag { + display: inline-block; + margin: 0 0 0 4px; + padding: 1px 2px; + cursor: pointer; + color: #000; +} + +.contact-form-editor-box-mail span.mailtag.used { + color: #666; +} + +.contact-form-editor-box-mail span.mailtag.unused { + font-weight: bold; +} + +/* + * Messages Tab + */ +#messages-panel p.description { + margin: 5px 0 10px; +} + +/* + * Tabs for integration modules + */ +#sendinblue-panel table tr.inactive ~ tr { + display: none; +} + +#sendinblue-panel .dashicons { + text-decoration: none; +} + +#sendinblue-panel td p { + margin-top: 12px; +} + +/* + * List Table + */ +.fixed .column-title { + width: 38%; +} + +.fixed .column-shortcode { + width: 38%; +} + +/* + * Welcome Panel + */ +.wpcf7-welcome-panel { + position: relative; + overflow: auto; + margin: 16px 0; + padding: 23px 10px 0; + border: 1px solid #c3c4c7; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + background: #fff; + font-size: 13px; + line-height: 1.7; +} + +.wpcf7-welcome-panel h3 { + font-size: 16px; + font-weight: 600; + line-height: 2.1em; + margin: 1em 0 1.2em; +} + +.wpcf7-welcome-panel h3 .dashicons { + position: relative; + top: -2px; + display: inline-block; + width: 60px; + color: #575757; + font-size: 40px; +} + +.wpcf7-welcome-panel p { + color: #646970; +} + +.wpcf7-welcome-panel p a { + font-weight: bold; +} + +.wpcf7-welcome-panel .welcome-panel-close { + position: absolute; + z-index: 2; + top: 10px; + right: 10px; + padding: 10px 15px 10px 21px; + font-size: 13px; + line-height: 1.23076923; /* Chrome rounding, needs to be 16px equivalent */ + text-decoration: none; +} + +.wpcf7-welcome-panel .welcome-panel-close::before { + background: 0 0; + color: #787c82; + content: "\f153" / ''; + display: block; + font: normal 16px/20px dashicons; + speak: never; + height: 20px; + text-align: center; + width: 20px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + position: absolute; + top: 8px; + left: 0; + transition: all .1s ease-in-out; +} + +.wpcf7-welcome-panel .welcome-panel-content { + display: block; + margin-left: 13px; + max-width: 1500px; + min-height: auto; +} + +.wpcf7-welcome-panel .welcome-panel-column-container { + clear: both; + position: relative; +} + +.wpcf7-welcome-panel .welcome-panel-column { + display: block; + width: 48%; + min-width: 200px; + float: left; + padding: 0 2% 0 0; + margin: 0 0 1em 0; +} + +@media screen and (max-width: 870px) { + .wpcf7-welcome-panel .welcome-panel-column { + display: block; + float: none; + width: 100%; + } +} + +.wpcf7-welcome-panel .welcome-panel-column p { + margin-top: 7px; + color: #3c434a; +} + +/* + * Integration + */ +.card { + background: #fff none repeat scroll 0 0; + border: 1px solid #e5e5e5; + border-left: 4px solid #e5e5e5; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + margin-top: 20px; + max-width: 520px; + min-width: 255px; + padding: 0.7em 2em 1em; + position: relative; +} + +.card.active { + border-color: #00a0d2; +} + +#constant_contact.card.active { + border-color: #dc3232; +} + +.card img.icon { + float: left; + margin: 8px 8px 8px -8px; +} + +.card h2.title { + float: left; + max-width: 240px; + font-size: 1.3em; + font-weight: 600; +} + +.card .infobox { + float: right; + font-size: 13px; + color: #666; + margin: 1em; + line-height: 1.5; + max-width: 240px; +} + +.card .inside .form-table th { + padding: 15px 10px 15px 0; + width: 160px; +} + +.card .inside .form-table td { + padding: 10px 10px; +} + +.card .checkboxes li { + margin: 0; +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/editor.php b/wp-content/plugins/contact-form-7/admin/includes/editor.php new file mode 100644 index 0000000..cb58d26 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/editor.php @@ -0,0 +1,556 @@ +contact_form = $contact_form; + } + + public function add_panel( $panel_id, $title, $callback ) { + if ( wpcf7_is_name( $panel_id ) ) { + $this->panels[$panel_id] = array( + 'title' => $title, + 'callback' => $callback, + ); + } + } + + public function display() { + if ( empty( $this->panels ) ) { + return; + } + + $active_panel_id = wpcf7_superglobal_get( 'active-tab' ); + + if ( ! array_key_exists( $active_panel_id, $this->panels ) ) { + $active_panel_id = array_key_first( $this->panels ); + } + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'nav', array( + 'id' => 'contact-form-editor-tabs', + 'role' => 'tablist', + 'aria-label' => __( 'Contact form editor tabs', 'contact-form-7' ), + 'data-active-tab' => absint( array_search( + $active_panel_id, array_keys( $this->panels ), true + ) ), + ) ); + + foreach ( $this->panels as $panel_id => $panel ) { + $active = $panel_id === $active_panel_id; + + $formatter->append_start_tag( 'button', array( + 'type' => 'button', + 'role' => 'tab', + 'aria-selected' => $active ? 'true' : 'false', + 'aria-controls' => $panel_id, + 'id' => sprintf( '%s-tab', $panel_id ), + 'tabindex' => $active ? '0' : '-1', + ) ); + + $formatter->append_preformatted( esc_html( $panel['title'] ) ); + } + + $formatter->end_tag( 'nav' ); + + foreach ( $this->panels as $panel_id => $panel ) { + $active = $panel_id === $active_panel_id; + + $formatter->append_start_tag( 'section', array( + 'role' => 'tabpanel', + 'aria-labelledby' => sprintf( '%s-tab', $panel_id ), + 'id' => $panel_id, + 'class' => 'contact-form-editor-panel', + 'tabindex' => '0', + 'hidden' => ! $active, + ) ); + + if ( is_callable( $panel['callback'] ) ) { + $formatter->call_user_func( $panel['callback'], $this->contact_form ); + } + + $formatter->end_tag( 'section' ); + } + + $formatter->print(); + } +} + +function wpcf7_editor_panel_form( $post ) { + $description = sprintf( + /* translators: %s: URL to support page about the form template */ + __( 'You can edit the form template here. For details, see Editing form template.', 'contact-form-7' ), + __( 'https://contactform7.com/editing-form-template/', 'contact-form-7' ) + ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'h2' ); + $formatter->append_preformatted( esc_html( __( 'Form', 'contact-form-7' ) ) ); + $formatter->end_tag( 'h2' ); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend' ); + $formatter->append_preformatted( $description ); + $formatter->end_tag( 'legend' ); + + $formatter->call_user_func( static function () { + $tag_generator = WPCF7_TagGenerator::get_instance(); + $tag_generator->print_buttons(); + } ); + + $formatter->append_start_tag( 'textarea', array( + 'id' => 'wpcf7-form', + 'name' => 'wpcf7-form', + 'cols' => 100, + 'rows' => 24, + 'class' => 'large-text code', + 'data-config-field' => 'form.body', + ) ); + + $formatter->append_preformatted( esc_textarea( $post->prop( 'form' ) ) ); + + $formatter->end_tag( 'textarea' ); + + $formatter->print(); +} + +function wpcf7_editor_panel_mail( $post ) { + wpcf7_editor_box_mail( $post ); + + echo '
'; + + wpcf7_editor_box_mail( $post, array( + 'id' => 'wpcf7-mail-2', + 'name' => 'mail_2', + 'title' => __( 'Mail (2)', 'contact-form-7' ), + 'use' => __( 'Use Mail (2)', 'contact-form-7' ), + ) ); +} + +function wpcf7_editor_box_mail( $post, $options = '' ) { + $options = wp_parse_args( $options, array( + 'id' => 'wpcf7-mail', + 'name' => 'mail', + 'title' => __( 'Mail', 'contact-form-7' ), + 'use' => null, + ) ); + + $id = esc_attr( $options['id'] ); + + $mail = wp_parse_args( $post->prop( $options['name'] ), array( + 'active' => false, + 'recipient' => '', + 'sender' => '', + 'subject' => '', + 'body' => '', + 'additional_headers' => '', + 'attachments' => '', + 'use_html' => false, + 'exclude_blank' => false, + ) ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'div', array( + 'class' => 'contact-form-editor-box-mail', + 'id' => $id, + ) ); + + $formatter->append_start_tag( 'h2' ); + $formatter->append_preformatted( esc_html( $options['title'] ) ); + $formatter->end_tag( 'h2' ); + + if ( ! empty( $options['use'] ) ) { + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-active', $id ), + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'id' => sprintf( '%s-active', $id ), + 'name' => sprintf( '%s[active]', $id ), + 'data-config-field' => '', + 'data-toggle' => sprintf( '%s-fieldset', $id ), + 'value' => '1', + 'checked' => $mail['active'], + ) ); + + $formatter->append_whitespace(); + $formatter->append_preformatted( esc_html( $options['use'] ) ); + $formatter->end_tag( 'label' ); + + $formatter->append_start_tag( 'p', array( + 'class' => 'description', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Mail (2) is an additional mail template often used as an autoresponder.', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'p' ); + } + + $formatter->append_start_tag( 'fieldset', array( + 'id' => sprintf( '%s-fieldset', $id ), + ) ); + + $formatter->append_start_tag( 'legend' ); + + $description = sprintf( + /* translators: %s: URL to support page about the email template */ + __( 'You can edit the email template here. For details, see Setting up mail.', 'contact-form-7' ), + __( 'https://contactform7.com/setting-up-mail/', 'contact-form-7' ) + ); + + $formatter->append_preformatted( $description ); + + $formatter->append_start_tag( 'br' ); + + $formatter->append_preformatted( + esc_html( __( 'In the following fields, you can use these mail-tags:', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'br' ); + + $formatter->call_user_func( static function () use ( $post, $options ) { + $post->suggest_mail_tags( $options['name'] ); + } ); + + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'table', array( + 'class' => 'form-table', + ) ); + + $formatter->append_start_tag( 'tbody' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-recipient', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'To', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => sprintf( '%s-recipient', $id ), + 'name' => sprintf( '%s[recipient]', $id ), + 'class' => 'large-text code', + 'size' => 70, + 'value' => $mail['recipient'], + 'data-config-field' => sprintf( '%s.recipient', $options['name'] ), + ) ); + + $formatter->end_tag( 'tr' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-sender', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'From', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => sprintf( '%s-sender', $id ), + 'name' => sprintf( '%s[sender]', $id ), + 'class' => 'large-text code', + 'size' => 70, + 'value' => $mail['sender'], + 'data-config-field' => sprintf( '%s.sender', $options['name'] ), + ) ); + + $formatter->end_tag( 'tr' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-subject', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Subject', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => sprintf( '%s-subject', $id ), + 'name' => sprintf( '%s[subject]', $id ), + 'class' => 'large-text code', + 'size' => 70, + 'value' => $mail['subject'], + 'data-config-field' => sprintf( '%s.subject', $options['name'] ), + ) ); + + $formatter->end_tag( 'tr' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-additional-headers', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Additional headers', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'textarea', array( + 'id' => sprintf( '%s-additional-headers', $id ), + 'name' => sprintf( '%s[additional_headers]', $id ), + 'cols' => 100, + 'rows' => 4, + 'class' => 'large-text code', + 'data-config-field' => sprintf( '%s.additional_headers', $options['name'] ), + ) ); + + $formatter->append_preformatted( + esc_textarea( $mail['additional_headers'] ) + ); + + $formatter->end_tag( 'tr' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-body', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Message body', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'textarea', array( + 'id' => sprintf( '%s-body', $id ), + 'name' => sprintf( '%s[body]', $id ), + 'cols' => 100, + 'rows' => 18, + 'class' => 'large-text code', + 'data-config-field' => sprintf( '%s.body', $options['name'] ), + ) ); + + $formatter->append_preformatted( + esc_textarea( $mail['body'] ) + ); + + $formatter->end_tag( 'textarea' ); + + $formatter->append_start_tag( 'p' ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-exclude-blank', $id ), + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'id' => sprintf( '%s-exclude-blank', $id ), + 'name' => sprintf( '%s[exclude_blank]', $id ), + 'value' => '1', + 'checked' => $mail['exclude_blank'], + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'Exclude a line from output if all of its mail-tags are blank', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'p' ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-use-html', $id ), + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'id' => sprintf( '%s-use-html', $id ), + 'name' => sprintf( '%s[use_html]', $id ), + 'value' => '1', + 'checked' => $mail['use_html'], + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'Use HTML content type', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'tr' ); + + $formatter->append_start_tag( 'tr' ); + + $formatter->append_start_tag( 'th', array( + 'scope' => 'row', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => sprintf( '%s-attachments', $id ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'File attachments', 'contact-form-7' ) ) + ); + + $formatter->append_start_tag( 'td' ); + + $formatter->append_start_tag( 'textarea', array( + 'id' => sprintf( '%s-attachments', $id ), + 'name' => sprintf( '%s[attachments]', $id ), + 'cols' => 100, + 'rows' => 4, + 'class' => 'large-text code', + 'data-config-field' => sprintf( '%s.attachments', $options['name'] ), + ) ); + + $formatter->append_preformatted( + esc_textarea( $mail['attachments'] ) + ); + + $formatter->end_tag( 'textarea' ); + $formatter->end_tag( 'tr' ); + + $formatter->print(); +} + +function wpcf7_editor_panel_messages( $post ) { + $description = sprintf( + /* translators: %s: URL to support page about the messages editor */ + __( 'You can edit messages used in various situations here. For details, see Editing messages.', 'contact-form-7' ), + __( 'https://contactform7.com/editing-messages/', 'contact-form-7' ) + ); + + $messages = wpcf7_messages(); + + if ( + isset( $messages['captcha_not_match'] ) and + ! wpcf7_use_really_simple_captcha() + ) { + unset( $messages['captcha_not_match'] ); + } + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'h2' ); + + $formatter->append_preformatted( + esc_html( __( 'Messages', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h2' ); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend' ); + $formatter->append_preformatted( $description ); + $formatter->end_tag( 'legend' ); + + foreach ( $messages as $key => $arr ) { + $field_id = sprintf( 'wpcf7-message-%s', strtr( $key, '_', '-' ) ); + $field_name = sprintf( 'wpcf7-messages[%s]', $key ); + + $formatter->append_start_tag( 'p', array( + 'class' => 'description', + ) ); + + $formatter->append_start_tag( 'label', array( + 'for' => $field_id, + ) ); + + $formatter->append_preformatted( esc_html( $arr['description'] ) ); + $formatter->append_start_tag( 'br' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'id' => $field_id, + 'name' => $field_name, + 'class' => 'large-text', + 'size' => 70, + 'value' => $post->message( $key, false ), + 'data-config-field' => sprintf( 'messages.%s', $key ), + ) ); + } + + $formatter->print(); +} + +function wpcf7_editor_panel_additional_settings( $post ) { + $description = sprintf( + /* translators: %s: URL to support page about the additional settings editor */ + __( 'You can add customization code snippets here. For details, see Additional settings.', 'contact-form-7' ), + __( 'https://contactform7.com/additional-settings/', 'contact-form-7' ) + ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'h2' ); + + $formatter->append_preformatted( + esc_html( __( 'Additional Settings', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'h2' ); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend' ); + $formatter->append_preformatted( $description ); + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'textarea', array( + 'id' => 'wpcf7-additional-settings', + 'name' => 'wpcf7-additional-settings', + 'cols' => 100, + 'rows' => 24, + 'class' => 'large-text', + 'data-config-field' => 'additional_settings.body', + ) ); + + $formatter->append_preformatted( + esc_textarea( $post->prop( 'additional_settings' ) ) + ); + + $formatter->end_tag( 'textarea' ); + + $formatter->print(); +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/help-tabs.php b/wp-content/plugins/contact-form-7/admin/includes/help-tabs.php new file mode 100644 index 0000000..9b768b1 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/help-tabs.php @@ -0,0 +1,104 @@ +screen = $screen; + } + + public function set_help_tabs( $screen_type ) { + switch ( $screen_type ) { + case 'list': + $this->screen->add_help_tab( array( + 'id' => 'list_overview', + 'title' => __( 'Overview', 'contact-form-7' ), + 'content' => $this->content( 'list_overview' ), + ) ); + + $this->screen->add_help_tab( array( + 'id' => 'list_available_actions', + 'title' => __( 'Available Actions', 'contact-form-7' ), + 'content' => $this->content( 'list_available_actions' ), + ) ); + + $this->sidebar(); + + return; + case 'edit': + $this->screen->add_help_tab( array( + 'id' => 'edit_overview', + 'title' => __( 'Overview', 'contact-form-7' ), + 'content' => $this->content( 'edit_overview' ), + ) ); + + $this->screen->add_help_tab( array( + 'id' => 'edit_form_tags', + 'title' => __( 'Form-tags', 'contact-form-7' ), + 'content' => $this->content( 'edit_form_tags' ), + ) ); + + $this->screen->add_help_tab( array( + 'id' => 'edit_mail_tags', + 'title' => __( 'Mail-tags', 'contact-form-7' ), + 'content' => $this->content( 'edit_mail_tags' ), + ) ); + + $this->sidebar(); + + return; + case 'integration': + $this->screen->add_help_tab( array( + 'id' => 'integration_overview', + 'title' => __( 'Overview', 'contact-form-7' ), + 'content' => $this->content( 'integration_overview' ), + ) ); + + $this->sidebar(); + + return; + } + } + + private function content( $name ) { + $content = array(); + + $content['list_overview'] = '

' . __( 'On this screen, you can manage contact forms provided by Contact Form 7. You can manage an unlimited number of contact forms. Each contact form has a unique ID and Contact Form 7 shortcode ([contact-form-7 ...]). To insert a contact form into a post or a text widget, insert the shortcode into the target.', 'contact-form-7' ) . '

'; + + $content['list_available_actions'] = '

' . __( 'Hovering over a row in the contact forms list will display action links that allow you to manage your contact form. You can perform the following actions:', 'contact-form-7' ) . '

'; + $content['list_available_actions'] .= '

' . __( 'Edit - Navigates to the editing screen for that contact form. You can also reach that screen by clicking on the contact form title.', 'contact-form-7' ) . '

'; + $content['list_available_actions'] .= '

' . __( 'Duplicate - Clones that contact form. A cloned contact form inherits all content from the original, but has a different ID.', 'contact-form-7' ) . '

'; + + $content['edit_overview'] = '

' . __( 'On this screen, you can edit a contact form. A contact form is comprised of the following components:', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'Title is the title of a contact form. This title is only used for labeling a contact form, and can be edited.', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'Form is a content of HTML form. You can use arbitrary HTML, which is allowed inside a form element. You can also use Contact Form 7’s form-tags here.', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'Mail manages a mail template (headers and message body) that this contact form will send when users submit it. You can use Contact Form 7’s mail-tags here.', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'Mail (2) is an additional mail template that works similar to Mail. Mail (2) is different in that it is sent only when Mail has been sent successfully.', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'In Messages, you can edit various types of messages used for this contact form. These messages are relatively short messages, like a validation error message you see when you leave a required field blank.', 'contact-form-7' ) . '

'; + $content['edit_overview'] .= '

' . __( 'Additional Settings provides a place where you can customize the behavior of this contact form by adding code snippets.', 'contact-form-7' ) . '

'; + + $content['edit_form_tags'] = '

' . __( 'A form-tag is a short code enclosed in square brackets used in a form content. A form-tag generally represents an input field, and its components can be separated into four parts: type, name, options, and values. Contact Form 7 supports several types of form-tags including text fields, number fields, date fields, checkboxes, radio buttons, menus, file-uploading fields, CAPTCHAs, and quiz fields.', 'contact-form-7' ) . '

'; + $content['edit_form_tags'] .= '

' . __( 'While form-tags have a comparatively complex syntax, you do not need to know the syntax to add form-tags because you can use the straightforward tag generator (Generate Tag button on this screen).', 'contact-form-7' ) . '

'; + + $content['edit_mail_tags'] = '

' . __( 'A mail-tag is also a short code enclosed in square brackets that you can use in every Mail and Mail (2) field. A mail-tag represents a user input value through an input field of a corresponding form-tag.', 'contact-form-7' ) . '

'; + $content['edit_mail_tags'] .= '

' . __( 'There are also special mail-tags that have specific names, but do not have corresponding form-tags. They are used to represent meta information of form submissions like the submitter’s IP address or the URL of the page.', 'contact-form-7' ) . '

'; + + $content['integration_overview'] = '

' . __( 'On this screen, you can manage services that are available through Contact Form 7. Using API will allow you to collaborate with any services that are available.', 'contact-form-7' ) . '

'; + $content['integration_overview'] .= '

' . __( 'You may need to first sign up for an account with the service that you plan to use. When you do so, you would need to authorize Contact Form 7 to access the service with your account.', 'contact-form-7' ) . '

'; + $content['integration_overview'] .= '

' . __( 'Any information you provide will not be shared with service providers without your authorization.', 'contact-form-7' ) . '

'; + + if ( ! empty( $content[$name] ) ) { + return $content[$name]; + } + } + + public function sidebar() { + $content = '

' . __( 'For more information:', 'contact-form-7' ) . '

'; + $content .= '

' . wpcf7_link( __( 'https://contactform7.com/docs/', 'contact-form-7' ), __( 'Docs', 'contact-form-7' ) ) . '

'; + $content .= '

' . wpcf7_link( __( 'https://contactform7.com/faq/', 'contact-form-7' ), __( 'FAQ', 'contact-form-7' ) ) . '

'; + $content .= '

' . wpcf7_link( __( 'https://contactform7.com/support/', 'contact-form-7' ), __( 'Support', 'contact-form-7' ) ) . '

'; + + $this->screen->set_help_sidebar( $content ); + } +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/js/index.asset.php b/wp-content/plugins/contact-form-7/admin/includes/js/index.asset.php new file mode 100644 index 0000000..61de87f --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/js/index.asset.php @@ -0,0 +1,9 @@ + array( + 'wp-api-fetch', + 'wp-i18n', + ), + 'version' => WPCF7_VERSION, +); diff --git a/wp-content/plugins/contact-form-7/admin/includes/js/index.js b/wp-content/plugins/contact-form-7/admin/includes/js/index.js new file mode 100644 index 0000000..7087e3d --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/js/index.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var r in a)e.o(a,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:a[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.i18n,a=document.querySelector("#contact-form-editor"),r=window.wp.apiFetch;var c=e.n(r);const o=e=>{const t=document.createElement("span");return t.classList.add("icon-in-circle"),t.setAttribute("aria-hidden","true"),t.append(e),t},n=e=>e.replace(/[^0-9a-z]+/gi,"-"),l=e=>{const t=document.querySelector(`#${e.dataset?.toggle}`);t&&(e.checked?t.classList.remove("hidden"):t.classList.add("hidden"))},i=()=>{document.querySelectorAll("#contact-form-editor .config-error, #misc-publishing-actions .config-error").forEach((e=>{e.remove()})),document.querySelectorAll("#contact-form-editor [data-config-field]").forEach((e=>{const t=e.dataset.configField;d(t).length?(e.setAttribute("aria-invalid","true"),e.setAttribute("aria-describedby",n(`wpcf7-config-error-for-${t}`)),e.after(u(t))):e.removeAttribute("aria-invalid")})),document.querySelectorAll("#contact-form-editor-tabs [data-panel]").forEach((e=>{e.querySelectorAll(".icon-in-circle").forEach((e=>{e.remove()})),s(e.dataset.panel)&&e.querySelector("a")?.append(o("!"))}));let e=0;if(document.querySelectorAll("#contact-form-editor .contact-form-editor-panel").forEach((a=>{const r=s(a.id);if(r){e+=r;const c=document.createElement("div");c.classList.add("config-error"),c.append(o("!"),(0,t.sprintf)((0,t._n)("%d configuration error detected in this tab panel.","%d configuration errors detected in this tab panel.",r,"contact-form-7"),r)),a.prepend(c)}})),e){const a=document.createElement("div");a.classList.add("misc-pub-section","config-error");const r=document.createElement("a");r.setAttribute("href",wpcf7.configValidator.docUrl),r.append((0,t.__)("How to resolve?","contact-form-7")),a.append(o("!"),(0,t.sprintf)((0,t._n)("%d configuration error detected.","%d configuration errors detected.",e,"contact-form-7"),e),document.createElement("br"),r),document.querySelector("#misc-publishing-actions")?.append(a)}},s=e=>document.querySelectorAll(`#${e} ul.config-error li`)?.length,d=e=>{const t=[];for(const a in wpcf7.configValidator.errors)a===e&&t.push(...wpcf7.configValidator.errors[a]);return t},u=e=>{if(!e)return"";const t=document.createElement("ul");return t.setAttribute("id",n(`wpcf7-config-error-for-${e}`)),t.classList.add("config-error"),d(e).forEach((e=>{if(!e.message)return;const a=document.createElement("li");if(a.append(o("!")),e.link){const t=document.createElement("a");t.setAttribute("href",e.link),t.append(e.message),a.append(" ",t)}else a.append(" ",e.message);t.append(a)})),t},m=document.querySelector("#wpcf7-welcome-panel"),p=e=>{const t=m.querySelector("#welcomepanelnonce")?.value;if(!t)return;const a=new FormData;a.append("action","wpcf7-update-welcome-panel"),a.append("visible",e),a.append("welcomepanelnonce",t),fetch(new Request(ajaxurl,{method:"POST",body:a})),e?m.classList.remove("hidden"):m.classList.add("hidden")},f=e=>{var t;const a=e.dataset.id,r=e.querySelector('[name="name"]');let c=null!==(t=r?.value.trim())&&void 0!==t?t:"";r&&(c||(c=`${a}-${Math.floor(1e3*Math.random())}`),r.value=c),e.querySelectorAll(".tag").forEach((t=>{const a=e.querySelector('[name="tagtype"]')?.value||t.name;a&&(t.value=v(a,e))})),e.querySelectorAll("span.mail-tag").forEach((e=>{e.innerText=`[${c}]`})),e.querySelectorAll("input.mail-tag").forEach((e=>{e.value=`[${c}]`}))},v=(e,t)=>{var a,r,c;const o=null!==(a=t.querySelector(`.scope.${e}`))&&void 0!==a?a:t,n=e+(t.querySelector('[name="required"]:checked')?"*":""),l=null!==(r=t.querySelector('[name="name"]')?.value)&&void 0!==r?r:"",i=[];o.querySelectorAll(".option").forEach((e=>{"checkbox"===e.type?e.checked&&i.push(e.name):"radio"===e.type?e.checked&&!e.classList.contains("default")&&i.push(`${e.name}:${e.value}`):""!==e.value&&(e.classList.contains("filetype")?i.push(`${e.name}:${e.value.split(/[,|\s]+/).join("|")}`):e.classList.contains("color")?i.push(`${e.name}:#${e.value}`):"class"===e.name?e.value.split(" ").forEach((e=>{i.push(`class:${e}`)})):i.push(`${e.name}:${e.value}`))})),"radio"===e&&i.push("default:1");const s=null!==(c=o.querySelector('[name="values"]')?.value.split("\n").map((e=>e.trim())).filter((e=>""!==e)).map((e=>`"${e.replace(/["]/g,""")}"`)))&&void 0!==c?c:[],d=[n,l,i.join(" "),s.join(" ")].map((e=>e.trim())).filter((e=>""!==e)),u=o.querySelector('[name="content"]')?.value.trim();return u?`[${d.join(" ")}] ${u} [/${n}]`:`[${d.join(" ")}]`},g=e=>{const t=h(e);t?(e.querySelectorAll('[data-tag-part="tag"]').forEach((e=>{e.value=t})),e.querySelectorAll('[data-taggen="insert-tag"]').forEach((e=>{e.disabled=!1})),e.querySelectorAll('[data-tag-part="mail-tag"]').forEach((t=>{const a=e.querySelector('[data-tag-part="name"]');a&&(t.innerText=`[${a.value.trim()}]`)}))):(e.querySelectorAll('[data-tag-part="tag"]').forEach((e=>{e.value=""})),e.querySelectorAll('[data-taggen="insert-tag"]').forEach((e=>{e.disabled=!0})))},h=e=>{var t,a;const r=e.querySelector('[data-tag-part="basetype"]')?.value.trim();if(!r)return;if(e.querySelector(":invalid"))return;let c=r;const o=e.querySelector('[data-tag-part="type-suffix"]');o&&(["checkbox","radio"].includes(o?.type)?c+=o.checked?o.value.trim():"":c+=o.value.trim());const n=e.querySelector('[data-tag-part="name"]');let l=null!==(t=n?.value.trim())&&void 0!==t?t:"";n&&(l||(l=`${r}-${Math.floor(1e3*Math.random())}`),n.value=l);const i=[];e.querySelectorAll('[data-tag-part="option"]').forEach((e=>{const t=e.dataset.tagOption?.trim();t&&(["checkbox","radio"].includes(e?.type)&&!e.checked||t.split(" ").forEach((t=>{t.endsWith(":")?e.value?.split(" ").forEach((e=>{(e=e.trim())&&i.push(t+e)})):i.push(t)})))}));const s=null!==(a=e.querySelector('[data-tag-part="value"]')?.value.split("\n").map((e=>e.trim())).filter((e=>""!==e)).map((e=>`"${e.replace(/["]/g,""")}"`)))&&void 0!==a?a:[],d=[c,l,i.join(" "),s.join(" ")].map((e=>e.trim())).filter((e=>""!==e)),u=e.querySelector('[data-tag-part="content"]')?.value.trim();return u?`[${d.join(" ")}] ${u} [/${c}]`:`[${d.join(" ")}]`},y=e=>{const t=document.querySelector("#wpcf7-admin-form-element");if(!t)return;let a=!1;t.querySelectorAll("input, textarea, select").forEach((e=>{if(!a)switch(e.type){case"button":case"hidden":case"image":case"reset":case"search":case"submit":break;case"checkbox":case"radio":a=e.defaultChecked!==e.checked;break;case"select-multiple":case"select-one":e.querySelectorAll("option").forEach((e=>{a||e.defaultSelected===e.selected||(a=!0)}));break;default:a=e.defaultValue!==e.value}})),a&&e.preventDefault()};document.addEventListener("DOMContentLoaded",(e=>{a&&a.querySelectorAll(':scope > [role="tablist"]').forEach((e=>{const t=e.querySelectorAll(':scope > [role="tab"]');let r=parseInt(e.dataset.activeTab);e.addEventListener("keydown",(a=>{["ArrowLeft","ArrowRight"].includes(a.key)&&(t[r].setAttribute("tabindex","-1"),"ArrowLeft"===a.key&&(r-=1,r<0&&(r=t.length-1)),"ArrowRight"===a.key&&(r+=1,t.length<=r&&(r=0)),t[r].setAttribute("tabindex","0"),t[r].focus(),e.dataset.activeTab=r)})),t.forEach((t=>{t.addEventListener("click",(r=>{const c=t.getAttribute("aria-controls");if(!c)return;const o=c.split(" ").reduceRight(((e,t)=>{const r=a.querySelector(`#${t}`);return r&&(e=r),e}),null);o&&(e.querySelectorAll(':scope > [aria-selected="true"]').forEach((e=>{e.setAttribute("aria-selected","false")})),t.setAttribute("aria-selected","true"),a.querySelectorAll(':scope > [role="tabpanel"]').forEach((e=>{e.setAttribute("hidden","hidden")})),o.removeAttribute("hidden"),document.querySelectorAll('input[name="active-tab"]').forEach((e=>{e.value=o.id})))}))}))})),document.querySelectorAll("#contact-form-editor [data-config-field]").forEach((e=>{e.addEventListener("change",(e=>{const t=document.querySelector('[name="post_ID"]')?.value;t&&0{const{namespace:t}=wpcf7.apiSettings,a=`/${t}/contact-forms/${e}`,r=new FormData;document.querySelectorAll("#contact-form-editor [data-config-field]").forEach((e=>{const t=e.name?.replace(/^wpcf7-/,"").replace(/-/g,"_");if(!t)return;let a;["checkbox","radio"].includes(e.type)?e.checked&&(a=e.value):a=e.value,void 0!==a&&(t.endsWith("[]")?r.append(t,a):r.set(t,a))})),r.set("context","dry-run"),c()({path:a,method:"POST",body:r}).then((e=>{wpcf7.configValidator.errors=e.config_errors,i()}))})(t)}))})),i(),(()=>{if(!m)return;const e=document.querySelector("#wpcf7-welcome-panel-show");m.querySelectorAll(".welcome-panel-close").forEach((t=>{t.addEventListener("click",(t=>{p(0),e?.removeAttribute("checked"),t.preventDefault()}))})),e?.addEventListener("click",(t=>{p(e?.checked?1:0)}))})(),document.querySelectorAll('[data-taggen="open-dialog"]').forEach((e=>{e.addEventListener("click",(t=>{const a=document.querySelector(`#${e.dataset.target}`);if(a){const e=a.querySelector("form.tag-generator-panel");e&&("1"===e.dataset.version?(e=>{f(e),e.querySelectorAll(".control-box").forEach((t=>{t.addEventListener("change",(t=>{var a;"name"===(a=t.target).name&&(a.value=a.value.replace(/[^0-9a-zA-Z:._-]/g,"").replace(/^[^a-zA-Z]+/,"")),a.classList.contains("numeric")&&(a.value=a.value.replace(/[^0-9.-]/g,"")),a.classList.contains("idvalue")&&(a.value=a.value.replace(/[^-0-9a-zA-Z_]/g,"")),a.classList.contains("classvalue")&&(a.value=a.value.split(" ").map((e=>e.replace(/[^-0-9a-zA-Z_]/g,""))).join(" ").replace(/\s+/g," ").trim()),a.classList.contains("color")&&(a.value=a.value.replace(/[^0-9a-fA-F]/g,"")),a.classList.contains("filesize")&&(a.value=a.value.replace(/[^0-9kKmMbB]/g,"")),a.classList.contains("filetype")&&(a.value=a.value.replace(/[^0-9a-zA-Z.,|\s]/g,"")),a.classList.contains("date")&&(a.value.match(/^\d{4}-\d{2}-\d{2}$/)||(a.value="")),"values"===a.name&&(a.value=a.value.trim()),f(e)}))}))})(e):"2"===e.dataset.version&&(e=>{var t;null!==(t=e.reset())&&void 0!==t||g(e),e.querySelectorAll(".control-box").forEach((t=>{t.addEventListener("change",(t=>{g(e)})),t.addEventListener("keyup",(t=>{var a;"text"!==(null!==(a=t.target.type)&&void 0!==a?a:"")&&"textarea"!==t.target.tagName?.toLowerCase()||g(e)}))}))})(e)),a.showModal()}}))})),document.querySelectorAll("dialog.tag-generator-dialog").forEach((e=>{e.querySelectorAll('[data-taggen="close-dialog"]').forEach((t=>{t.addEventListener("click",(t=>e.close("")))})),e.querySelectorAll('[data-taggen="insert-tag"], .insert-tag').forEach((t=>{t.addEventListener("click",(t=>{const a=e.querySelector('[data-tag-part="tag"], .tag');e.close(a?.value)}))})),e.addEventListener("close",(t=>{var a;const r=document.querySelector("textarea#wpcf7-form");if(null===r)return;if(""===e.returnValue)return;const c=null!==(a=r.selectionEnd)&&void 0!==a?a:0;0===c&&(e.returnValue+="\n\n"),r.value=r.value.substring(0,c)+e.returnValue+r.value.substring(c),r.selectionStart=c,r.selectionEnd=c+e.returnValue.length,r.focus()}))})),(()=>{const e=document.querySelector("#wpcf7-admin-form-element");e&&(window.addEventListener("beforeunload",y),e.addEventListener("submit",(e=>{e.submitter?.name&&"wpcf7-copy"===e.submitter.name||window.removeEventListener("beforeunload",y)})))})();const r=document.querySelector("input#title");r&&""===r.value&&r.focus(),document.querySelector("#wpcf7-admin-form-element")?.addEventListener("submit",(e=>{const a=document.querySelector('#wpcf7-admin-form-element [name="action"]'),r=document.querySelector('#wpcf7-admin-form-element [name="_wpnonce"]');"wpcf7-save"===e.submitter?.name&&(a&&(a.value="save"),r&&(r.value=wpcf7.nonce.save),document.querySelectorAll("#wpcf7-admin-form-element #publishing-action .spinner").forEach((e=>{e.classList.add("is-active")}))),"wpcf7-copy"===e.submitter?.name&&(a&&(a.value="copy"),r&&(r.value=wpcf7.nonce.copy)),"wpcf7-delete"===e.submitter?.name&&(window.confirm((0,t.__)("You are about to delete this contact form.\n 'Cancel' to stop, 'OK' to delete.","contact-form-7"))?(a&&(a.value="delete"),r&&(r.value=wpcf7.nonce.delete)):e.preventDefault())})),document.querySelectorAll('.contact-form-editor-box-mail span.mailtag, [data-tag-part="mail-tag"]').forEach((e=>{e.addEventListener("click",(t=>{const a=document.createRange();a.selectNodeContents(e),window.getSelection().addRange(a)}))})),document.querySelectorAll("input.selectable").forEach((e=>{e.addEventListener("click",(t=>{e.focus(),e.select()}))})),document.querySelectorAll("[data-toggle]").forEach((e=>{l(e),e.addEventListener("change",(t=>{l(e)}))})),document.querySelectorAll("#wpcf7-sendinblue-enable-contact-list, #wpcf7-sendinblue-enable-transactional-email").forEach((e=>{e.addEventListener("change",(t=>{e.checked?e.closest("tr").classList.remove("inactive"):e.closest("tr").classList.add("inactive")}))}))}))})(); \ No newline at end of file diff --git a/wp-content/plugins/contact-form-7/admin/includes/tag-generator.php b/wp-content/plugins/contact-form-7/admin/includes/tag-generator.php new file mode 100644 index 0000000..066b5c1 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/tag-generator.php @@ -0,0 +1,627 @@ + '1', + ) ); + + $this->panels[$id] = array( + 'title' => $title, + 'content' => 'tag-generator-panel-' . $id, + 'options' => $options, + 'callback' => $callback, + ); + + if ( version_compare( $options['version'], '2', '<' ) ) { + $message = sprintf( + /* translators: 1: version, 2: tag generator title */ + __( 'Use of tag generator instances older than version 2 is deprecated. Version %1$s instance (%2$s) detected.', 'contact-form-7' ), + $options['version'], + $title + ); + + wp_trigger_error( __METHOD__, $message, E_USER_DEPRECATED ); + } + + return true; + } + + + /** + * Renders form-tag generator calling buttons. + */ + public function print_buttons() { + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'span', array( + 'id' => 'tag-generator-list', + 'class' => 'hide-if-no-js', + ) ); + + foreach ( (array) $this->panels as $panel ) { + $formatter->append_start_tag( 'button', array( + 'type' => 'button', + 'data-taggen' => 'open-dialog', + 'data-target' => $panel['content'], + 'title' => sprintf( + /* translators: %s: title of form-tag */ + __( 'Form-tag Generator: %s', 'contact-form-7' ), + $panel['title'] + ), + ) ); + + $formatter->append_preformatted( esc_html( $panel['title'] ) ); + } + + $formatter->print(); + } + + + /** + * Renders form-tag generator dialog panels (hidden until called). + */ + public function print_panels( WPCF7_ContactForm $contact_form ) { + $formatter = new WPCF7_HTMLFormatter( array( + 'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array( + 'dialog' => array( + 'id' => true, + 'class' => true, + ), + 'form' => array( + 'method' => true, + 'class' => true, + 'data-*' => true, + ), + ) ), + ) ); + + foreach ( (array) $this->panels as $id => $panel ) { + $callback = $panel['callback']; + + $options = array_merge( $panel['options'], array( + 'id' => $id, + 'title' => $panel['title'], + 'content' => $panel['content'], + ) ); + + if ( is_callable( $callback ) ) { + $formatter->append_start_tag( 'dialog', array( + 'id' => $options['content'], + 'class' => 'tag-generator-dialog', + ) ); + + $formatter->append_start_tag( 'button', array( + 'class' => 'close-button', + 'title' => __( 'Close this dialog box', 'contact-form-7' ), + 'data-taggen' => 'close-dialog', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Close', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'button' ); + + $formatter->append_start_tag( 'form', array( + 'method' => 'dialog', + 'class' => 'tag-generator-panel', + 'data-id' => $options['id'], + 'data-version' => $options['version'], + ) ); + + $formatter->call_user_func( $callback, $contact_form, $options ); + + $formatter->close_all_tags(); + } + } + + $formatter->print(); + } + +} + + +/** + * Class helps to implement a form-tag generator content. + */ +class WPCF7_TagGeneratorGenerator { + + private $key = ''; + + + /** + * The constructor. + */ + public function __construct( $key ) { + $this->key = $key; + } + + + /** + * Returns a unique reference ID. + */ + public function ref( $suffix = '' ) { + $ref = sprintf( '%s-%s', $this->key, $suffix ); + $ref = strtolower( $ref ); + $ref = preg_replace( '/[^0-9a-z-]/', '', $ref ); + $ref = preg_replace( '/[-]+/', '-', $ref ); + $ref = trim( $ref, '-' ); + return $ref; + } + + + /** + * Calls one of the template methods. + */ + public function print( $part, $options = '' ) { + if ( is_callable( array( $this, $part ) ) ) { + call_user_func( array( $this, $part ), $options ); + } + } + + + /** + * Template method for field type field. + */ + private function field_type( $options = '' ) { + $options = wp_parse_args( $options, array( + 'with_required' => false, + 'with_optional' => false, + 'select_options' => array(), + ) ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend', array( + 'id' => $this->ref( 'type-legend' ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Field type', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'select', array( + 'data-tag-part' => 'basetype', + 'aria-labelledby' => $this->ref( 'type-legend' ), + ) ); + + foreach ( (array) $options['select_options'] as $basetype => $title ) { + $formatter->append_start_tag( 'option', array( + 'value' => $basetype, + ) ); + + $formatter->append_preformatted( esc_html( $title ) ); + } + + $formatter->end_tag( 'select' ); + + if ( $options['with_required'] ) { + $formatter->append_start_tag( 'br' ); + $formatter->append_start_tag( 'label' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'data-tag-part' => 'type-suffix', + 'value' => '*', + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'This is a required field.', 'contact-form-7' ) ) + ); + } + + if ( $options['with_optional'] ) { + $formatter->append_start_tag( 'br' ); + $formatter->append_start_tag( 'label' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'data-tag-part' => 'option', + 'data-tag-option' => 'optional', + 'checked' => true, + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'This checkbox is optional.', 'contact-form-7' ) ) + ); + } + + $formatter->print(); + } + + + /** + * Template method for field name field. + */ + private function field_name( $options = '' ) { + $options = wp_parse_args( $options, array( + 'ask_if' => '', + ) ); + +?> +
+ + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ 'number', + 'title' => __( 'Length', 'contact-form-7' ), + 'min_option' => 'minlength:', + 'max_option' => 'maxlength:', + 'accept_minus' => false, + ) ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend' ); + $formatter->append_preformatted( esc_html( $options['title'] ) ); + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'label' ); + + $formatter->append_preformatted( + esc_html( __( 'Min', 'contact-form-7' ) ) + ); + + $formatter->append_whitespace(); + + $formatter->append_start_tag( 'input', array( + 'type' => $options['type'], + 'data-tag-part' => 'option', + 'data-tag-option' => $options['min_option'], + 'min' => $options['accept_minus'] ? null : 0, + ) ); + + $formatter->end_tag( 'label' ); + + $formatter->append_preformatted( ' ⇔ ' ); + + $formatter->append_start_tag( 'label' ); + + $formatter->append_preformatted( + esc_html( __( 'Max', 'contact-form-7' ) ) + ); + + $formatter->append_whitespace(); + + $formatter->append_start_tag( 'input', array( + 'type' => $options['type'], + 'data-tag-part' => 'option', + 'data-tag-option' => $options['max_option'], + 'min' => $options['accept_minus'] ? null : 0, + ) ); + + $formatter->end_tag( 'label' ); + + $formatter->print(); + } + + + /** + * Template method for default value field. + */ + private function default_value( $options = '' ) { + $options = wp_parse_args( $options, array( + 'type' => 'text', + 'title' => __( 'Default value', 'contact-form-7' ), + 'with_placeholder' => false, + 'use_content' => false, + ) ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend', array( + 'id' => $this->ref( 'value-legend' ), + ) ); + + $formatter->append_preformatted( esc_html( $options['title'] ) ); + + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'input', array( + 'type' => $options['type'], + 'data-tag-part' => $options['use_content'] ? 'content' : 'value', + 'aria-labelledby' => $this->ref( 'value-legend' ), + ) ); + + if ( $options['with_placeholder'] ) { + $formatter->append_start_tag( 'br' ); + $formatter->append_start_tag( 'label' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'data-tag-part' => 'option', + 'data-tag-option' => 'placeholder', + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'Use this text as the placeholder.', 'contact-form-7' ) ) + ); + } + + $formatter->print(); + } + + + /** + * Template method for selectable values useful for checkboxes or a menu. + */ + private function selectable_values( $options = '' ) { + $options = wp_parse_args( $options, array( + 'first_as_label' => false, + 'use_label_element' => false, + ) ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'fieldset' ); + + $formatter->append_start_tag( 'legend', array( + 'id' => $this->ref( 'selectable-values-legend' ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Selectable values', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'legend' ); + + $formatter->append_start_tag( 'span', array( + 'id' => $this->ref( 'selectable-values-description' ), + ) ); + + $formatter->append_preformatted( + esc_html( __( 'One item per line.', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'span' ); + + $formatter->append_start_tag( 'br' ); + + $formatter->append_start_tag( 'textarea', array( + 'required' => true, + 'data-tag-part' => 'value', + 'aria-labelledby' => $this->ref( 'selectable-values-legend' ), + 'aria-describedby' => $this->ref( 'selectable-values-description' ), + ) ); + + $formatter->append_preformatted( + esc_html( __( "Option 1\nOption 2\nOption 3", 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'textarea' ); + + if ( $options['first_as_label'] ) { + $formatter->append_start_tag( 'br' ); + $formatter->append_start_tag( 'label' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'checked' => 'checked' === $options['first_as_label'], + 'data-tag-part' => 'option', + 'data-tag-option' => 'first_as_label', + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'Use the first item as a label.', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'label' ); + } + + if ( $options['use_label_element'] ) { + $formatter->append_start_tag( 'br' ); + $formatter->append_start_tag( 'label' ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'checkbox', + 'checked' => 'checked' === $options['use_label_element'], + 'data-tag-part' => 'option', + 'data-tag-option' => 'use_label_element', + ) ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( + esc_html( __( 'Wrap each item with a label element.', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'label' ); + } + + $formatter->print(); + } + + + /** + * Template method for insert-box content including the result form-tag. + */ + private function insert_box_content( $options = '' ) { + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'div', array( + 'class' => 'flex-container', + ) ); + + $formatter->append_start_tag( 'input', array( + 'type' => 'text', + 'class' => 'code selectable', + 'readonly' => true, + 'data-tag-part' => 'tag', + 'aria-label' => __( 'The form-tag to be inserted into the form template', 'contact-form-7' ), + ) ); + + $formatter->append_whitespace(); + + $formatter->append_start_tag( 'button', array( + 'type' => 'button', + 'class' => 'button button-primary', + 'data-taggen' => 'insert-tag', + ) ); + + $formatter->append_preformatted( + esc_html( __( 'Insert Tag', 'contact-form-7' ) ) + ); + + $formatter->end_tag( 'button' ); + + $formatter->print(); + } + + + /** + * Template method for a tip message about mail-tag. + */ + private function mail_tag_tip( $options = '' ) { + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'p', array( + 'class' => 'mail-tag-tip', + ) ); + + $formatter->append_preformatted( sprintf( + /* translators: %s: mail-tag corresponding to the form-tag */ + esc_html( __( 'To use the user input in the email, insert the corresponding mail-tag %s into the email template.', 'contact-form-7' ) ), + '' + ) ); + + $formatter->print(); + } + +} diff --git a/wp-content/plugins/contact-form-7/admin/includes/welcome-panel.php b/wp-content/plugins/contact-form-7/admin/includes/welcome-panel.php new file mode 100644 index 0000000..ae6ffc9 --- /dev/null +++ b/wp-content/plugins/contact-form-7/admin/includes/welcome-panel.php @@ -0,0 +1,280 @@ +content(); + + if ( is_array( $content ) ) { + $content = implode( "\n\n", $content ); + } + + $content = wp_kses_post( $content ); + $content = wptexturize( $content ); + $content = convert_chars( $content ); + $content = wpautop( $content ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'div', array( + 'class' => 'welcome-panel-column', + ) ); + + $formatter->append_start_tag( 'h3' ); + + $formatter->append_start_tag( 'span', array( + 'class' => sprintf( 'dashicons dashicons-%s', $this->icon() ), + 'aria-hidden' => 'true', + ) ); + + $formatter->end_tag( 'span' ); + + $formatter->append_whitespace(); + + $formatter->append_preformatted( $this->title() ); + + $formatter->end_tag( 'h3' ); + + $formatter->append_preformatted( $content ); + + $formatter->print(); + } +} + + +class WPCF7_WelcomePanelColumn_AntiSpam extends WPCF7_WelcomePanelColumn { + + protected function icon() { + return 'shield'; + } + + protected function title() { + return __( 'Getting spammed? You have protection.', 'contact-form-7' ); + } + + protected function content() { + return array( + __( 'Spammers target everything; your contact forms are not an exception. Before you get spammed, protect your contact forms with the powerful anti-spam features Contact Form 7 provides.', 'contact-form-7' ), + sprintf( + /* translators: 1: URL to support page about Akismet, 2: Cloudflare Turnstile, 3: Disallowed list */ + __( 'Contact Form 7 supports spam-filtering with Akismet. Cloudflare Turnstile blocks annoying spambots. Plus, using disallowed list, you can block messages containing specified keywords or those sent from specified IP addresses.', 'contact-form-7' ), + __( 'https://contactform7.com/spam-filtering-with-akismet/', 'contact-form-7' ), + __( 'https://contactform7.com/turnstile-integration/', 'contact-form-7' ), + __( 'https://contactform7.com/comment-blacklist/', 'contact-form-7' ) + ), + ); + } +} + + +class WPCF7_WelcomePanelColumn_Donation extends WPCF7_WelcomePanelColumn { + + protected function icon() { + return 'megaphone'; + } + + protected function title() { + return __( 'Contact Form 7 needs your support.', 'contact-form-7' ); + } + + protected function content() { + return array( + __( 'It is hard to continue to maintain this plugin without support from users like you.', 'contact-form-7' ), + sprintf( + /* translators: %s: URL to support page about ways to contribute */ + __( 'There are several ways for you to contribute to the project: testing, coding, translating it into your local languages, helping other users, financial donations, etc, etc. We equally welcome you regardless of the way you contribute.', 'contact-form-7' ), + __( 'https://contactform7.com/contributing/', 'contact-form-7' ) + ), + ); + } +} + + +class WPCF7_WelcomePanelColumn_Flamingo extends WPCF7_WelcomePanelColumn { + + protected function icon() { + return 'editor-help'; + } + + protected function title() { + return __( 'Before you cry over spilt mail…', 'contact-form-7' ); + } + + protected function content() { + return array( + __( 'Contact Form 7 does not store submitted messages anywhere. Therefore, you may lose important messages forever if your mail server has issues or you make a mistake in mail configuration.', 'contact-form-7' ), + sprintf( + /* translators: %s: URL to support page about the Flamingo plugin */ + __( 'Install a message storage plugin before this happens to you. Flamingo saves all messages through contact forms into the database. Flamingo is a free WordPress plugin created by the same author as Contact Form 7.', 'contact-form-7' ), + __( 'https://contactform7.com/save-submitted-messages-with-flamingo/', 'contact-form-7' ) + ), + ); + } +} + + +class WPCF7_WelcomePanelColumn_Integration extends WPCF7_WelcomePanelColumn { + + protected function icon() { + return 'superhero-alt'; + } + + protected function title() { + return __( 'You have strong allies to back you up.', 'contact-form-7' ); + } + + protected function content() { + return array( + sprintf( + /* translators: %s: URL to support page about the Brevo integration */ + __( 'Your contact forms will become more powerful and versatile by integrating them with external APIs. With CRM and email marketing services, you can build your own contact lists (Brevo).', 'contact-form-7' ), + __( 'https://contactform7.com/sendinblue-integration/', 'contact-form-7' ) + ), + sprintf( + /* translators: 1: URL to support page about Cloudflare Turnstile, 2: Stripe */ + __( 'With help from cloud-based machine learning, anti-spam services will protect your forms (Cloudflare Turnstile). Even payment services are natively supported (Stripe).', 'contact-form-7' ), + __( 'https://contactform7.com/turnstile-integration/', 'contact-form-7' ), + __( 'https://contactform7.com/stripe-integration/', 'contact-form-7' ) + ), + ); + } +} + + +function wpcf7_welcome_panel() { + $columns = array(); + + $flamingo_is_active = defined( 'FLAMINGO_VERSION' ); + + $sendinblue_is_active = false; + + if ( + class_exists( 'WPCF7_Sendinblue' ) and + $sendinblue = WPCF7_Sendinblue::get_instance() + ) { + $sendinblue_is_active = $sendinblue->is_active(); + } + + if ( $flamingo_is_active and $sendinblue_is_active ) { + $columns[] = new WPCF7_WelcomePanelColumn_AntiSpam(); + $columns[] = new WPCF7_WelcomePanelColumn_Donation(); + } elseif ( $flamingo_is_active ) { + $columns[] = new WPCF7_WelcomePanelColumn_Integration(); + $columns[] = new WPCF7_WelcomePanelColumn_AntiSpam(); + } elseif ( $sendinblue_is_active ) { + $columns[] = new WPCF7_WelcomePanelColumn_Flamingo(); + $columns[] = new WPCF7_WelcomePanelColumn_AntiSpam(); + } else { + $columns[] = new WPCF7_WelcomePanelColumn_Flamingo(); + $columns[] = new WPCF7_WelcomePanelColumn_Integration(); + } + + $classes = 'wpcf7-welcome-panel'; + + $vers = (array) get_user_meta( get_current_user_id(), + 'wpcf7_hide_welcome_panel_on', true + ); + + if ( wpcf7_version_grep( wpcf7_version( 'only_major=1' ), $vers ) ) { + $classes .= ' hidden'; + } + +?> +
+ + + +
+
+print_content(); + } + +?> +
+
+
+id ) { + return $screen_settings; + } + + $vers = (array) get_user_meta( get_current_user_id(), + 'wpcf7_hide_welcome_panel_on', true + ); + + $checkbox_id = 'wpcf7-welcome-panel-show'; + $checked = ! in_array( wpcf7_version( 'only_major=1' ), $vers, true ); + + $checkbox = sprintf( + '', + wpcf7_format_atts( array( + 'id' => $checkbox_id, + 'type' => 'checkbox', + 'checked' => $checked, + ) ) + ); + + $screen_settings .= sprintf( ' +
+%1$s + +
', + esc_html( __( 'Welcome panel', 'contact-form-7' ) ), + esc_attr( $checkbox_id ), + $checkbox, + esc_html( __( 'Show welcome panel', 'contact-form-7' ) ) + ); + + return $screen_settings; +} diff --git a/wp-content/plugins/contact-form-7/assets/icon.png b/wp-content/plugins/contact-form-7/assets/icon.png new file mode 100644 index 0000000..f66eefa Binary files /dev/null and b/wp-content/plugins/contact-form-7/assets/icon.png differ diff --git a/wp-content/plugins/contact-form-7/assets/icon.svg b/wp-content/plugins/contact-form-7/assets/icon.svg new file mode 100644 index 0000000..33df0bd --- /dev/null +++ b/wp-content/plugins/contact-form-7/assets/icon.svg @@ -0,0 +1 @@ + diff --git a/wp-content/plugins/contact-form-7/includes/block-editor/block-editor.php b/wp-content/plugins/contact-form-7/includes/block-editor/block-editor.php new file mode 100644 index 0000000..05a1794 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/block-editor/block-editor.php @@ -0,0 +1,13 @@ + array( + 'react-jsx-runtime', + 'wp-api-fetch', + 'wp-block-editor', + 'wp-blocks', + 'wp-components', + 'wp-element', + 'wp-i18n', + 'wp-url', + ), + 'version' => WPCF7_VERSION, +); diff --git a/wp-content/plugins/contact-form-7/includes/block-editor/index.js b/wp-content/plugins/contact-form-7/includes/block-editor/index.js new file mode 100644 index 0000000..eb68299 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/block-editor/index.js @@ -0,0 +1 @@ +(()=>{"use strict";var t={n:e=>{var l=e&&e.__esModule?()=>e.default:()=>e;return t.d(l,{a:l}),l},d:(e,l)=>{for(var a in l)t.o(l,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:l[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};const e=window.wp.i18n,l=window.wp.blocks,a=window.wp.blockEditor,r=window.ReactJSXRuntime,o=(0,r.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 242.5 239.46",children:[(0,r.jsx)("defs",{children:(0,r.jsx)("clipPath",{id:"clip-path",transform:"translate(1.72)",children:(0,r.jsx)("circle",{className:"cls-1",cx:"119.73",cy:"119.73",r:"116.15",fill:"none"})})}),(0,r.jsx)("g",{id:"Layer_2","data-name":"Layer 2",children:(0,r.jsxs)("g",{id:"Layer_1","data-name":"Layer 1",children:[(0,r.jsxs)("g",{className:"cls-2",clipPath:"url(#clip-path)",children:[(0,r.jsx)("circle",{className:"cls-3",cx:"121.45",cy:"119.73",r:"116.15",fill:"#33c6f4"}),(0,r.jsx)("path",{className:"cls-4",d:"M239.32,167.79c-53.41-24-108.37-91.46-113-94.55s-10.84.77-10.84.77c-3.87-6.19-10.06.77-10.06.77C76.77,123.55.14,170.11.14,170.11S36.94,237.79,122,237.79C208.48,237.79,239.32,167.79,239.32,167.79Z",transform:"translate(1.72)",fill:"#1b447e"}),(0,r.jsx)("path",{className:"cls-5",d:"M67.48,116.58s15.48-7,12.38,4.65-15.48,28.64-11.61,29.41S83,140.58,86.06,142.12s5.42.78,3.87,6.2-3.1,9.29,0,9.29,5.42-7,9.29-13.94,10.06-3.87,12.38-1.55,9.29,15.49,14.71,13.94,8.51-8.52,6.19-24,1.55-20.12,1.55-20.12,4.64-2.32,13.16,8.51,24,27.09,26.31,26.32-10.83-17.8-7.74-19.35,15.48,2.32,21.68,7.74c0,0,2.12,8.87,2.12.36L126.31,73.24,115.47,74l-10.06.77S80.64,111.94,67.48,116.58Z",transform:"translate(1.72)",fill:"#fff"}),(0,r.jsx)("path",{className:"cls-6",d:"M239.32,170.11c-53.41-24-108.37-93.78-113-96.87s-10.84.77-10.84.77c-3.87-6.19-10.06.77-10.06.77C76.77,123.55.14,170.11.14,170.11",transform:"translate(1.72)",fill:"none",stroke:"#221e1f",strokeMiterlimit:"10",strokeWidth:"8px"})]}),(0,r.jsx)("circle",{className:"cls-6",cx:"121.45",cy:"119.73",r:"116.15",fill:"none",stroke:"#1b447e",strokeMiterlimit:"10",strokeWidth:"8px"})]})})]}),s=window.wp.element,c=window.wp.components,n=window.wp.apiFetch;var i=t.n(n);const m=window.wp.url,h=async t=>i()({path:(0,m.addQueryArgs)("/contact-form-7/v1/contact-forms",{posts_per_page:20,orderby:"modified",order:"DESC",...t})}).then((t=>t)),d=t=>{let e="[contact-form-7]";return t.hash?e=e.replace(/\]$/,` id="${t.hash}"]`):t.id&&(e=e.replace(/\]$/,` id="${t.id}"]`)),t.title&&(e=e.replace(/\]$/,` title="${t.title}"]`)),t.htmlId&&(e=e.replace(/\]$/,` html_id="${t.htmlId}"]`)),t.htmlName&&(e=e.replace(/\]$/,` html_name="${t.htmlName}"]`)),t.htmlTitle&&(e=e.replace(/\]$/,` html_title="${t.htmlTitle}"]`)),t.htmlClass&&(e=e.replace(/\]$/,` html_class="${t.htmlClass}"]`)),"raw_form"===t.output&&(e=e.replace(/\]$/,` output="${t.output}"]`)),e},p=t=>{const e=ajaxurl.replace(/\/admin-ajax\.php$/,"/admin.php");return(0,m.addQueryArgs)(e,{page:"wpcf7",post:t.id,action:"edit"})},f={from:[],to:[{type:"block",blocks:["core/shortcode"],transform:t=>{const e=d(t);return(0,l.createBlock)("core/shortcode",{text:e})}}]};(0,l.registerBlockType)("contact-form-7/contact-form-selector",{icon:o,transforms:f,edit:function({attributes:t,setAttributes:l}){const o=t=>t.reduce(((t,e)=>t.set(e.id,e)),new Map),[n,i]=(0,s.useState)((()=>o([])));return(0,s.useEffect)((()=>{h().then((t=>{i(o(t))}))}),[]),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(a.InspectorControls,{children:[t.id&&(0,r.jsx)(c.PanelBody,{title:t.title,children:(0,r.jsx)(c.ExternalLink,{href:p(t),children:(0,e.__)("Edit this contact form","contact-form-7")})}),t.id&&(0,r.jsxs)(c.PanelBody,{title:(0,e.__)("Form attributes","contact-form-7"),initialOpen:!1,children:[(0,r.jsx)(c.TextControl,{label:(0,e.__)("ID","contact-form-7"),value:t.htmlId,onChange:t=>l({htmlId:t}),help:(0,e.__)("Used for the id attribute value of the form element.","contact-form-7")}),(0,r.jsx)(c.TextControl,{label:(0,e.__)("Name","contact-form-7"),value:t.htmlName,onChange:t=>l({htmlName:t}),help:(0,e.__)("Used for the name attribute value of the form element.","contact-form-7")}),(0,r.jsx)(c.TextControl,{label:(0,e.__)("Title","contact-form-7"),value:t.htmlTitle,onChange:t=>l({htmlTitle:t}),help:(0,e.__)("Used for the aria-label attribute value of the form element.","contact-form-7")}),(0,r.jsx)(c.TextControl,{label:(0,e.__)("Class","contact-form-7"),value:t.htmlClass,onChange:t=>l({htmlClass:t}),help:(0,e.__)("Used for the class attribute value of the form element.","contact-form-7")})]})]}),(0,r.jsx)("div",{...(0,a.useBlockProps)({className:"components-placeholder",style:{marginTop:"28px",marginBottom:"28px"}}),children:(0,r.jsx)(c.ComboboxControl,{label:(0,e.__)("Select a contact form:","contact-form-7"),options:(t=>{const e=[];for(const[l,a]of t)e.push({value:l,label:a.title});return e})(n),value:t.id,onChange:t=>l({id:parseInt(t),hash:n.get(parseInt(t))?.hash,title:n.get(parseInt(t))?.title}),onFilterValueChange:t=>{h({search:t}).then((t=>{i(o(t))}))}})})]})},save:({attributes:t})=>{const e=d(t);return(0,r.jsx)("div",{...a.useBlockProps.save(),children:e})}})})(); \ No newline at end of file diff --git a/wp-content/plugins/contact-form-7/includes/capabilities.php b/wp-content/plugins/contact-form-7/includes/capabilities.php new file mode 100644 index 0000000..09a478b --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/capabilities.php @@ -0,0 +1,26 @@ + WPCF7_ADMIN_READ_WRITE_CAPABILITY, + 'wpcf7_edit_contact_forms' => WPCF7_ADMIN_READ_WRITE_CAPABILITY, + 'wpcf7_read_contact_form' => WPCF7_ADMIN_READ_CAPABILITY, + 'wpcf7_read_contact_forms' => WPCF7_ADMIN_READ_CAPABILITY, + 'wpcf7_delete_contact_form' => WPCF7_ADMIN_READ_WRITE_CAPABILITY, + 'wpcf7_delete_contact_forms' => WPCF7_ADMIN_READ_WRITE_CAPABILITY, + 'wpcf7_manage_integration' => 'manage_options', + 'wpcf7_submit' => 'read', + ); + + $meta_caps = apply_filters( 'wpcf7_map_meta_cap', $meta_caps ); + + $caps = array_diff( $caps, array_keys( $meta_caps ) ); + + if ( isset( $meta_caps[$cap] ) ) { + $caps[] = $meta_caps[$cap]; + } + + return $caps; +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/actions.php b/wp-content/plugins/contact-form-7/includes/config-validator/actions.php new file mode 100644 index 0000000..d045f75 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/actions.php @@ -0,0 +1,27 @@ + 'unsafe_email_without_protection', + ); + + foreach ( $contact_forms as $contact_form ) { + $config_validator = new WPCF7_ConfigValidator( $contact_form, $options ); + $config_validator->restore(); + $config_validator->validate(); + $config_validator->save(); + } + } +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/additional-settings.php b/wp-content/plugins/contact-form-7/includes/config-validator/additional-settings.php new file mode 100644 index 0000000..0d0c788 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/additional-settings.php @@ -0,0 +1,28 @@ +supports( 'deprecated_settings' ) ) { + $deprecated_settings_used = + $this->contact_form->additional_setting( 'on_sent_ok' ) || + $this->contact_form->additional_setting( 'on_submit' ); + + if ( $deprecated_settings_used ) { + $this->add_error( $section, 'deprecated_settings', + array( + 'message' => __( 'Deprecated settings are used.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'deprecated_settings' ); + } + } + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/form.php b/wp-content/plugins/contact-form-7/includes/config-validator/form.php new file mode 100644 index 0000000..a480456 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/form.php @@ -0,0 +1,278 @@ +contact_form->prop( 'form' ); + + if ( $this->supports( 'multiple_controls_in_label' ) ) { + if ( $this->detect_multiple_controls_in_label( $section, $form ) ) { + $this->add_error( $section, 'multiple_controls_in_label', + array( + 'message' => __( 'Multiple form controls are in a single label element.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'multiple_controls_in_label' ); + } + } + + if ( $this->supports( 'unavailable_names' ) ) { + $ng_names = $this->detect_unavailable_names( $section, $form ); + + if ( $ng_names ) { + $this->add_error( $section, 'unavailable_names', + array( + 'message' => + /* translators: %names%: a list of form control names */ + __( 'Unavailable names (%names%) are used for form controls.', 'contact-form-7' ), + 'params' => array( 'names' => implode( ', ', $ng_names ) ), + ) + ); + } else { + $this->remove_error( $section, 'unavailable_names' ); + } + } + + if ( $this->supports( 'unavailable_html_elements' ) ) { + if ( $this->detect_unavailable_html_elements( $section, $form ) ) { + $this->add_error( $section, 'unavailable_html_elements', + array( + 'message' => __( 'Unavailable HTML elements are used in the form template.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'unavailable_html_elements' ); + } + } + + if ( $this->supports( 'dots_in_names' ) ) { + if ( $this->detect_dots_in_names( $section, $form ) ) { + $this->add_error( $section, 'dots_in_names', + array( + 'message' => __( 'Dots are used in form-tag names.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'dots_in_names' ); + } + } + + if ( $this->supports( 'colons_in_names' ) ) { + if ( $this->detect_colons_in_names( $section, $form ) ) { + $this->add_error( $section, 'colons_in_names', + array( + 'message' => __( 'Colons are used in form-tag names.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'colons_in_names' ); + } + } + + if ( $this->supports( 'upload_filesize_overlimit' ) ) { + if ( $this->detect_upload_filesize_overlimit( $section, $form ) ) { + $this->add_error( $section, 'upload_filesize_overlimit', + array( + 'message' => __( 'Uploadable file size exceeds PHP’s maximum acceptable size.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'upload_filesize_overlimit' ); + } + } + } + + + /** + * Detects errors of multiple form controls in a single label. + * + * @link https://contactform7.com/configuration-errors/multiple-controls-in-label/ + */ + public function detect_multiple_controls_in_label( $section, $content ) { + $pattern = '%(.+?)%s'; + + if ( preg_match_all( $pattern, $content, $matches ) ) { + $form_tags_manager = WPCF7_FormTagsManager::get_instance(); + + foreach ( $matches[1] as $insidelabel ) { + $tags = $form_tags_manager->scan( $insidelabel ); + $fields_count = 0; + + foreach ( $tags as $tag ) { + $is_multiple_controls_container = wpcf7_form_tag_supports( + $tag->type, 'multiple-controls-container' + ); + + $is_zero_controls_container = wpcf7_form_tag_supports( + $tag->type, 'zero-controls-container' + ); + + if ( $is_multiple_controls_container ) { + $fields_count += count( $tag->values ); + + if ( $tag->has_option( 'free_text' ) ) { + $fields_count += 1; + } + } elseif ( $is_zero_controls_container ) { + $fields_count += 0; + } elseif ( ! empty( $tag->name ) ) { + $fields_count += 1; + } + + if ( 1 < $fields_count ) { + return true; + } + } + } + } + + return false; + } + + + /** + * Detects errors of unavailable form-tag names. + * + * @link https://contactform7.com/configuration-errors/unavailable-names/ + */ + public function detect_unavailable_names( $section, $content ) { + $public_query_vars = array( 'm', 'p', 'posts', 'w', 'cat', + 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', + 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', + 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', + 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', + 'pagename', 'page_id', 'error', 'attachment', 'attachment_id', + 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', + 'cpage', 'post_type', 'embed', + ); + + $form_tags_manager = WPCF7_FormTagsManager::get_instance(); + + $ng_named_tags = $form_tags_manager->filter( $content, array( + 'name' => $public_query_vars, + ) ); + + $ng_names = array(); + + foreach ( $ng_named_tags as $tag ) { + $ng_names[] = sprintf( '"%s"', $tag->name ); + } + + if ( $ng_names ) { + return array_unique( $ng_names ); + } + + return false; + } + + + /** + * Detects errors of unavailable HTML elements. + * + * @link https://contactform7.com/configuration-errors/unavailable-html-elements/ + */ + public function detect_unavailable_html_elements( $section, $content ) { + $pattern = '%(?:]|)%i'; + + if ( preg_match( $pattern, $content ) ) { + return true; + } + + return false; + } + + + /** + * Detects errors of dots in form-tag names. + * + * @link https://contactform7.com/configuration-errors/dots-in-names/ + */ + public function detect_dots_in_names( $section, $content ) { + $form_tags_manager = WPCF7_FormTagsManager::get_instance(); + + $tags = $form_tags_manager->filter( $content, array( + 'feature' => 'name-attr', + ) ); + + foreach ( $tags as $tag ) { + if ( str_contains( $tag->raw_name, '.' ) ) { + return true; + } + } + + return false; + } + + + /** + * Detects errors of colons in form-tag names. + * + * @link https://contactform7.com/configuration-errors/colons-in-names/ + */ + public function detect_colons_in_names( $section, $content ) { + $form_tags_manager = WPCF7_FormTagsManager::get_instance(); + + $tags = $form_tags_manager->filter( $content, array( + 'feature' => 'name-attr', + ) ); + + foreach ( $tags as $tag ) { + if ( str_contains( $tag->raw_name, ':' ) ) { + return true; + } + } + + return false; + } + + + /** + * Detects errors of uploadable file size overlimit. + * + * @link https://contactform7.com/configuration-errors/upload-filesize-overlimit + */ + public function detect_upload_filesize_overlimit( $section, $content ) { + $upload_max_filesize = ini_get( 'upload_max_filesize' ); + + if ( ! $upload_max_filesize ) { + return false; + } + + $upload_max_filesize = strtolower( $upload_max_filesize ); + $upload_max_filesize = trim( $upload_max_filesize ); + + if ( ! preg_match( '/^(\d+)([kmg]?)$/', $upload_max_filesize, $matches ) ) { + return false; + } + + if ( 'k' === $matches[2] ) { + $upload_max_filesize = (int) $matches[1] * KB_IN_BYTES; + } elseif ( 'm' === $matches[2] ) { + $upload_max_filesize = (int) $matches[1] * MB_IN_BYTES; + } elseif ( 'g' === $matches[2] ) { + $upload_max_filesize = (int) $matches[1] * GB_IN_BYTES; + } else { + $upload_max_filesize = (int) $matches[1]; + } + + $form_tags_manager = WPCF7_FormTagsManager::get_instance(); + + $tags = $form_tags_manager->filter( $content, array( + 'basetype' => 'file', + ) ); + + foreach ( $tags as $tag ) { + if ( $upload_max_filesize < $tag->get_limit_option() ) { + return true; + } + } + + return false; + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/mail.php b/wp-content/plugins/contact-form-7/includes/config-validator/mail.php new file mode 100644 index 0000000..131810f --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/mail.php @@ -0,0 +1,583 @@ + false, + 'callback' => + array( $this, 'replace_mail_tags_with_minimum_input_callback' ), + ) ); + + $content = new WPCF7_MailTaggedText( $content, $options ); + + return $content->replace_tags(); + } + + + /** + * Callback function for WPCF7_MailTaggedText. Replaces mail-tags with + * the most conservative inputs. + */ + public function replace_mail_tags_with_minimum_input_callback( $matches ) { + // allow [[foo]] syntax for escaping a tag + if ( '[' === $matches[1] and ']' === $matches[4] ) { + return substr( $matches[0], 1, -1 ); + } + + $tag = $matches[0]; + $tagname = $matches[2]; + $values = $matches[3]; + + $mail_tag = new WPCF7_MailTag( $tag, $tagname, $values ); + $field_name = $mail_tag->field_name(); + + $example_email = 'example@example.com'; + $example_text = 'example'; + $example_blank = ''; + + // for back-compat + $field_name = preg_replace( '/^wpcf7\./', '_', $field_name ); + + if ( '_site_admin_email' === $field_name ) { + return get_bloginfo( 'admin_email', 'raw' ); + + } elseif ( '_user_agent' === $field_name ) { + return $example_text; + + } elseif ( '_user_email' === $field_name ) { + return $this->contact_form->is_true( 'subscribers_only' ) + ? $example_email + : $example_blank; + + } elseif ( str_starts_with( $field_name, '_user_' ) ) { + return $this->contact_form->is_true( 'subscribers_only' ) + ? $example_text + : $example_blank; + + } elseif ( str_starts_with( $field_name, '_' ) ) { + return str_ends_with( $field_name, '_email' ) + ? $example_email + : $example_text; + + } + + static $opcalcset = array(); + + if ( ! isset( $opcalcset[$this->contact_form->id()] ) ) { + $opcalcset[$this->contact_form->id()] = + new WPCF7_MailTag_OutputCalculator( $this->contact_form ); + } + + $opcalc = $opcalcset[$this->contact_form->id()]; + $op = $opcalc->calc_output( $mail_tag ); + + if ( WPCF7_MailTag_OutputCalculator::email === $op ) { + return $example_email; + } elseif ( ! ( WPCF7_MailTag_OutputCalculator::blank & $op ) ) { + return $example_text; + } else { + return $example_blank; + } + } + + + /** + * Runs error detection for the mail sections. + */ + public function validate_mail( $template = 'mail' ) { + if ( + $this->contact_form->is_true( 'demo_mode' ) or + $this->contact_form->is_true( 'skip_mail' ) + ) { + return; + } + + $components = (array) $this->contact_form->prop( $template ); + + if ( ! $components ) { + return; + } + + if ( 'mail' !== $template and empty( $components['active'] ) ) { + return; + } + + $components = wp_parse_args( $components, array( + 'subject' => '', + 'sender' => '', + 'recipient' => '', + 'additional_headers' => '', + 'body' => '', + 'attachments' => '', + ) ); + + $this->validate_mail_subject( + $template, + $components['subject'] + ); + + $this->validate_mail_sender( + $template, + $components['sender'] + ); + + $this->validate_mail_recipient( + $template, + $components['recipient'] + ); + + $this->validate_mail_additional_headers( + $template, + $components['additional_headers'] + ); + + $this->validate_mail_body( + $template, + $components['body'] + ); + + $this->validate_mail_attachments( + $template, + $components['attachments'] + ); + } + + + /** + * Runs error detection for the mail subject section. + */ + public function validate_mail_subject( $template, $content ) { + $section = sprintf( '%s.subject', $template ); + + if ( $this->supports( 'maybe_empty' ) ) { + if ( $this->detect_maybe_empty( $section, $content ) ) { + $this->add_error( $section, 'maybe_empty', + array( + 'message' => __( 'There is a possible empty field.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'maybe_empty' ); + } + } + } + + + /** + * Runs error detection for the mail sender section. + */ + public function validate_mail_sender( $template, $content ) { + $section = sprintf( '%s.sender', $template ); + + if ( $this->supports( 'invalid_mailbox_syntax' ) ) { + if ( $this->detect_invalid_mailbox_syntax( $section, $content ) ) { + $this->add_error( $section, 'invalid_mailbox_syntax', + array( + 'message' => __( 'Invalid mailbox syntax is used.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'invalid_mailbox_syntax' ); + } + } + + if ( $this->supports( 'email_not_in_site_domain' ) ) { + $this->remove_error( $section, 'email_not_in_site_domain' ); + + if ( ! $this->has_error( $section, 'invalid_mailbox_syntax' ) ) { + $sender = $this->replace_mail_tags( $content ); + $sender = wpcf7_strip_newline( $sender ); + + if ( ! wpcf7_is_email_in_site_domain( $sender ) ) { + $this->add_error( $section, 'email_not_in_site_domain', + array( + 'message' => __( 'Sender email address does not belong to the site domain.', 'contact-form-7' ), + ) + ); + } + } + } + } + + + /** + * Runs error detection for the mail recipient section. + */ + public function validate_mail_recipient( $template, $content ) { + $section = sprintf( '%s.recipient', $template ); + + if ( $this->supports( 'invalid_mailbox_syntax' ) ) { + if ( $this->detect_invalid_mailbox_syntax( $section, $content ) ) { + $this->add_error( $section, 'invalid_mailbox_syntax', + array( + 'message' => __( 'Invalid mailbox syntax is used.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'invalid_mailbox_syntax' ); + } + } + + if ( $this->supports( 'unsafe_email_without_protection' ) ) { + $this->remove_error( $section, 'unsafe_email_without_protection' ); + + if ( ! $this->has_error( $section, 'invalid_mailbox_syntax' ) ) { + if ( + $this->detect_unsafe_email_without_protection( $section, $content ) + ) { + $this->add_error( $section, 'unsafe_email_without_protection', + array( + 'message' => __( 'Unsafe email config is used without sufficient protection.', 'contact-form-7' ), + ) + ); + } + } + } + } + + + /** + * Runs error detection for the mail additional headers section. + */ + public function validate_mail_additional_headers( $template, $content ) { + $section = sprintf( '%s.additional_headers', $template ); + + $invalid_mail_headers = array(); + $invalid_mailbox_fields = array(); + $unsafe_email_fields = array(); + + foreach ( explode( "\n", $content ) as $header ) { + $header = trim( $header ); + + if ( '' === $header ) { + continue; + } + + $is_valid_header = preg_match( + '/^([0-9A-Za-z-]+):(.*)$/', + $header, + $matches + ); + + if ( ! $is_valid_header ) { + $invalid_mail_headers[] = $header; + continue; + } + + $header_name = $matches[1]; + $header_value = trim( $matches[2] ); + + if ( + in_array( + strtolower( $header_name ), array( 'reply-to', 'cc', 'bcc' ), true + ) and + '' !== $header_value and + $this->detect_invalid_mailbox_syntax( $section, $header_value ) + ) { + $invalid_mailbox_fields[] = $header_name; + continue; + } + + if ( + in_array( strtolower( $header_name ), array( 'cc', 'bcc' ), true ) and + $this->detect_unsafe_email_without_protection( $section, $header_value ) + ) { + $unsafe_email_fields[] = $header_name; + } + } + + if ( $this->supports( 'invalid_mail_header' ) ) { + if ( ! empty( $invalid_mail_headers ) ) { + $this->add_error( $section, 'invalid_mail_header', + array( + 'message' => __( 'There are invalid mail header fields.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'invalid_mail_header' ); + } + } + + if ( $this->supports( 'invalid_mailbox_syntax' ) ) { + if ( ! empty( $invalid_mailbox_fields ) ) { + foreach ( $invalid_mailbox_fields as $header_name ) { + $this->add_error( $section, 'invalid_mailbox_syntax', + array( + 'message' => __( 'Invalid mailbox syntax is used in the %name% field.', 'contact-form-7' ), + 'params' => array( 'name' => $header_name ), + ) + ); + } + } else { + $this->remove_error( $section, 'invalid_mailbox_syntax' ); + } + } + + if ( $this->supports( 'unsafe_email_without_protection' ) ) { + if ( ! empty( $unsafe_email_fields ) ) { + $this->add_error( $section, 'unsafe_email_without_protection', + array( + 'message' => __( 'Unsafe email config is used without sufficient protection.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'unsafe_email_without_protection' ); + } + } + } + + + /** + * Runs error detection for the mail body section. + */ + public function validate_mail_body( $template, $content ) { + $section = sprintf( '%s.body', $template ); + + if ( $this->supports( 'maybe_empty' ) ) { + if ( $this->detect_maybe_empty( $section, $content ) ) { + $this->add_error( $section, 'maybe_empty', + array( + 'message' => __( 'There is a possible empty field.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'maybe_empty' ); + } + } + } + + + /** + * Runs error detection for the mail attachments section. + */ + public function validate_mail_attachments( $template, $content ) { + $section = sprintf( '%s.attachments', $template ); + + $total_size = 0; + $files_not_found = array(); + $files_out_of_content = array(); + + if ( '' !== $content ) { + $attachables = array(); + + $tags = $this->contact_form->scan_form_tags( + array( 'type' => array( 'file', 'file*' ) ) + ); + + foreach ( $tags as $tag ) { + $name = $tag->name; + + if ( ! str_contains( $content, "[{$name}]" ) ) { + continue; + } + + $limit = (int) $tag->get_limit_option(); + + if ( empty( $attachables[$name] ) or $attachables[$name] < $limit ) { + $attachables[$name] = $limit; + } + } + + $total_size = array_sum( $attachables ); + + foreach ( explode( "\n", $content ) as $line ) { + $line = trim( $line ); + + if ( '' === $line or str_starts_with( $line, '[' ) ) { + continue; + } + + if ( $this->detect_file_not_found( $section, $line ) ) { + $files_not_found[] = $line; + } elseif ( $this->detect_file_not_in_content_dir( $section, $line ) ) { + $files_out_of_content[] = $line; + } else { + $total_size += (int) @filesize( path_join( WP_CONTENT_DIR, $line ) ); + } + } + } + + if ( $this->supports( 'file_not_found' ) ) { + if ( ! empty( $files_not_found ) ) { + foreach ( $files_not_found as $line ) { + $this->add_error( $section, 'file_not_found', + array( + 'message' => __( 'Attachment file does not exist at %path%.', 'contact-form-7' ), + 'params' => array( 'path' => $line ), + ) + ); + } + } else { + $this->remove_error( $section, 'file_not_found' ); + } + } + + if ( $this->supports( 'file_not_in_content_dir' ) ) { + if ( ! empty( $files_out_of_content ) ) { + $this->add_error( $section, 'file_not_in_content_dir', + array( + 'message' => __( 'It is not allowed to use files outside the wp-content directory.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'file_not_in_content_dir' ); + } + } + + if ( $this->supports( 'attachments_overweight' ) ) { + $max = 25 * MB_IN_BYTES; // 25 MB + + if ( $max < $total_size ) { + $this->add_error( $section, 'attachments_overweight', + array( + 'message' => __( 'The total size of attachment files is too large.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'attachments_overweight' ); + } + } + } + + + /** + * Detects errors of invalid mailbox syntax. + * + * @link https://contactform7.com/configuration-errors/invalid-mailbox-syntax/ + */ + public function detect_invalid_mailbox_syntax( $section, $content ) { + $content = $this->replace_mail_tags( $content ); + $content = wpcf7_strip_newline( $content ); + + if ( ! wpcf7_is_mailbox_list( $content ) ) { + return true; + } + + return false; + } + + + /** + * Detects errors of empty message fields. + * + * @link https://contactform7.com/configuration-errors/maybe-empty/ + */ + public function detect_maybe_empty( $section, $content ) { + $content = $this->replace_mail_tags( $content ); + $content = wpcf7_strip_newline( $content ); + + if ( '' === $content ) { + return true; + } + + return false; + } + + + /** + * Detects errors of nonexistent attachment files. + * + * @link https://contactform7.com/configuration-errors/file-not-found/ + */ + public function detect_file_not_found( $section, $content ) { + $path = path_join( WP_CONTENT_DIR, $content ); + + if ( ! is_readable( $path ) or ! is_file( $path ) ) { + return true; + } + + return false; + } + + + /** + * Detects errors of attachment files out of the content directory. + * + * @link https://contactform7.com/configuration-errors/file-not-in-content-dir/ + */ + public function detect_file_not_in_content_dir( $section, $content ) { + $path = path_join( WP_CONTENT_DIR, $content ); + + if ( ! wpcf7_is_file_path_in_content_dir( $path ) ) { + return true; + } + + return false; + } + + + /** + * Detects errors of that unsafe email config is used without + * sufficient protection. + * + * @link https://contactform7.com/configuration-errors/unsafe-email-without-protection/ + */ + public function detect_unsafe_email_without_protection( $section, $content ) { + if ( $this->contact_form->is_true( 'subscribers_only' ) ) { + return false; + } + + static $is_captcha_active = null; + + if ( null === $is_captcha_active ) { + $is_captcha_active = call_user_func( static function () { + $recaptcha = WPCF7_RECAPTCHA::get_instance(); + $turnstile = WPCF7_Turnstile::get_instance(); + return $recaptcha->is_active() || $turnstile->is_active(); + } ); + } + + if ( $is_captcha_active ) { + return false; + } + + $example_email = 'user-specified@example.com'; + + // Replace mail-tags connected to an email type form-tag first. + $content = $this->replace_mail_tags( $content, array( + 'callback' => function ( $matches ) use ( $example_email ) { + // allow [[foo]] syntax for escaping a tag + if ( '[' === $matches[1] and ']' === $matches[4] ) { + return substr( $matches[0], 1, -1 ); + } + + $tag = $matches[0]; + $tagname = $matches[2]; + $values = $matches[3]; + + $mail_tag = new WPCF7_MailTag( $tag, $tagname, $values ); + $field_name = $mail_tag->field_name(); + + $form_tags = $this->contact_form->scan_form_tags( + array( 'name' => $field_name ) + ); + + if ( $form_tags ) { + $form_tag = new WPCF7_FormTag( $form_tags[0] ); + + if ( 'email' === $form_tag->basetype ) { + return $example_email; + } + } + + return $tag; + }, + ) ); + + // Replace remaining mail-tags. + $content = $this->replace_mail_tags( $content ); + + $content = wpcf7_strip_newline( $content ); + + if ( str_contains( $content, $example_email ) ) { + return true; + } + + return false; + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/messages.php b/wp-content/plugins/contact-form-7/includes/config-validator/messages.php new file mode 100644 index 0000000..0194d2c --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/messages.php @@ -0,0 +1,55 @@ +contact_form->prop( 'messages' ); + + if ( ! $messages ) { + return; + } + + if ( + isset( $messages['captcha_not_match'] ) and + ! wpcf7_use_really_simple_captcha() + ) { + unset( $messages['captcha_not_match'] ); + } + + foreach ( $messages as $key => $message ) { + $section = sprintf( 'messages.%s', $key ); + + if ( $this->supports( 'html_in_message' ) ) { + if ( $this->detect_html_in_message( $section, $message ) ) { + $this->add_error( $section, 'html_in_message', + array( + 'message' => __( 'HTML tags are used in a message.', 'contact-form-7' ), + ) + ); + } else { + $this->remove_error( $section, 'html_in_message' ); + } + } + } + } + + + /** + * Detects errors of HTML uses in a message. + * + * @link https://contactform7.com/configuration-errors/html-in-message/ + */ + public function detect_html_in_message( $section, $content ) { + $stripped = wp_strip_all_tags( $content ); + + if ( $stripped !== $content ) { + return true; + } + + return false; + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/config-validator/validator.php b/wp-content/plugins/contact-form-7/includes/config-validator/validator.php new file mode 100644 index 0000000..cc33294 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/config-validator/validator.php @@ -0,0 +1,386 @@ + null, + 'exclude' => null, + ) ); + + $this->contact_form = $contact_form; + + if ( isset( $options['include'] ) ) { + $this->include = (array) $options['include']; + } + + if ( isset( $options['exclude'] ) ) { + $this->exclude = (array) $options['exclude']; + } + } + + + /** + * Returns the contact form object that is tied to this validator. + */ + public function contact_form() { + return $this->contact_form; + } + + + /** + * Returns true if no error has been detected. + */ + public function is_valid() { + return ! $this->count_errors(); + } + + + /** + * Returns true if the given error code is supported by this instance. + */ + public function supports( $error_code ) { + if ( isset( $this->include ) ) { + $supported_codes = array_intersect( self::error_codes, $this->include ); + } else { + $supported_codes = self::error_codes; + } + + if ( isset( $this->exclude ) ) { + $supported_codes = array_diff( $supported_codes, $this->exclude ); + } + + return in_array( $error_code, $supported_codes, true ); + } + + + /** + * Counts detected errors. + */ + public function count_errors( $options = '' ) { + $options = wp_parse_args( $options, array( + 'section' => '', + 'code' => '', + ) ); + + $count = 0; + + foreach ( $this->errors as $key => $errors ) { + if ( preg_match( '/^mail_[0-9]+\.(.*)$/', $key, $matches ) ) { + $key = sprintf( 'mail.%s', $matches[1] ); + } + + if ( + $options['section'] and + $key !== $options['section'] and + preg_replace( '/\..*$/', '', $key, 1 ) !== $options['section'] + ) { + continue; + } + + foreach ( $errors as $error ) { + if ( empty( $error ) ) { + continue; + } + + if ( $options['code'] and $error['code'] !== $options['code'] ) { + continue; + } + + $count += 1; + } + } + + return $count; + } + + + /** + * Collects messages for detected errors. + */ + public function collect_error_messages( $options = '' ) { + $options = wp_parse_args( $options, array( + 'decodes_html_entities' => false, + ) ); + + $error_messages = array(); + + foreach ( $this->errors as $section => $errors ) { + $error_messages[$section] = array(); + + foreach ( $errors as $error ) { + if ( empty( $error['args']['message'] ) ) { + $message = $this->get_default_message( $error['code'] ); + } elseif ( empty( $error['args']['params'] ) ) { + $message = $error['args']['message']; + } else { + $message = $this->build_message( + $error['args']['message'], + $error['args']['params'] + ); + } + + if ( $options['decodes_html_entities'] ) { + $message = html_entity_decode( $message, ENT_HTML5 ); + } + + $link = ''; + + if ( ! empty( $error['args']['link'] ) ) { + $link = $error['args']['link']; + } + + $error_messages[$section][] = array( + 'message' => $message, + 'link' => esc_url( $link ), + ); + } + } + + return $error_messages; + } + + + /** + * Builds an error message by replacing placeholders. + */ + public function build_message( $message, $params = '' ) { + $params = wp_parse_args( $params, array() ); + + foreach ( $params as $key => $val ) { + if ( ! preg_match( '/^[0-9A-Za-z_]+$/', $key ) ) { // invalid key + continue; + } + + $placeholder = '%' . $key . '%'; + + if ( false !== stripos( $message, $placeholder ) ) { + $message = str_ireplace( $placeholder, $val, $message ); + } + } + + return $message; + } + + + /** + * Returns a default message that is used when the message for the error + * is not specified. + */ + public function get_default_message( $code = '' ) { + return __( 'Configuration error is detected.', 'contact-form-7' ); + } + + + /** + * Returns true if the specified section has the specified error. + * + * @param string $section The section where the error detected. + * @param string $code The unique code of the error. + */ + public function has_error( $section, $code ) { + if ( empty( $this->errors[$section] ) ) { + return false; + } + + foreach ( (array) $this->errors[$section] as $error ) { + if ( isset( $error['code'] ) and $error['code'] === $code ) { + return true; + } + } + + return false; + } + + + /** + * Adds a validation error. + * + * @param string $section The section where the error detected. + * @param string $code The unique code of the error. + * @param string|array $args Optional options for the error. + */ + public function add_error( $section, $code, $args = '' ) { + $args = wp_parse_args( $args, array( + 'message' => '', + 'params' => array(), + ) ); + + $available_error_codes = (array) apply_filters( + 'wpcf7_config_validator_available_error_codes', + self::error_codes, + $this->contact_form + ); + + if ( ! in_array( $code, $available_error_codes, true ) ) { + return false; + } + + if ( ! isset( $args['link'] ) ) { + $args['link'] = self::get_doc_link( $code ); + } + + if ( ! isset( $this->errors[$section] ) ) { + $this->errors[$section] = array(); + } + + $this->errors[$section][] = array( + 'code' => $code, + 'args' => $args, + ); + + return true; + } + + + /** + * Removes an error. + * + * @param string $section The section where the error detected. + * @param string $code The unique code of the error. + */ + public function remove_error( $section, $code ) { + if ( empty( $this->errors[$section] ) ) { + return; + } + + foreach ( (array) $this->errors[$section] as $key => $error ) { + if ( isset( $error['code'] ) and $error['code'] === $code ) { + unset( $this->errors[$section][$key] ); + } + } + + if ( empty( $this->errors[$section] ) ) { + unset( $this->errors[$section] ); + } + } + + + /** + * The main validation runner. + * + * @return bool True if there is no error detected. + */ + public function validate() { + $this->validate_form(); + $this->validate_mail( 'mail' ); + $this->validate_mail( 'mail_2' ); + $this->validate_messages(); + $this->validate_additional_settings(); + + do_action( 'wpcf7_config_validator_validate', $this ); + + return $this->is_valid(); + } + + + /** + * Saves detected errors as a post meta data. + */ + public function save() { + if ( $this->contact_form->initial() ) { + return; + } + + delete_post_meta( $this->contact_form->id(), '_config_validation' ); + + if ( $this->errors ) { + update_post_meta( + $this->contact_form->id(), '_config_validation', $this->errors + ); + } + } + + + /** + * Restore errors from the database. + */ + public function restore() { + $config_errors = get_post_meta( + $this->contact_form->id(), '_config_validation', true + ); + + foreach ( (array) $config_errors as $section => $errors ) { + if ( empty( $errors ) ) { + continue; + } + + foreach ( (array) $errors as $error ) { + if ( ! empty( $error['code'] ) ) { + $code = $error['code']; + $args = isset( $error['args'] ) ? $error['args'] : ''; + $this->add_error( $section, $code, $args ); + } + } + } + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/contact-form-functions.php b/wp-content/plugins/contact-form-7/includes/contact-form-functions.php new file mode 100644 index 0000000..f054faa --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/contact-form-functions.php @@ -0,0 +1,476 @@ + array( + array( + 'key' => '_old_cf7_unit_id', + 'type' => 'DECIMAL', + 'value' => $old_id, + ), + ), + 'posts_per_page' => 1, + ) ); + + if ( $contact_forms ) { + return wpcf7_contact_form( $contact_forms[0] ); + } +} + + +/** + * Searches for a contact form by a hash string. + * + * @param string $hash Part of a hash string. + * @return WPCF7_ContactForm Contact form object. + */ +function wpcf7_get_contact_form_by_hash( $hash ) { + if ( ! preg_match( '/^[0-9a-f]{7,}$/', $hash ) ) { + return null; + } + + $contact_forms = WPCF7_ContactForm::find( array( + 'meta_query' => array( + array( + 'key' => '_hash', + 'compare' => 'REGEXP', + 'value' => '^' . $hash, + ), + ), + 'posts_per_page' => 1, + ) ); + + if ( $contact_forms ) { + return wpcf7_contact_form( $contact_forms[0] ); + } +} + + +/** + * Searches for a contact form by title. + * + * @param string $title Title of contact form. + * @return WPCF7_ContactForm|null Contact form object if found, null otherwise. + */ +function wpcf7_get_contact_form_by_title( $title ) { + if ( ! is_string( $title ) or '' === $title ) { + return null; + } + + $contact_forms = WPCF7_ContactForm::find( array( + 'title' => $title, + 'posts_per_page' => 1, + ) ); + + if ( $contact_forms ) { + return wpcf7_contact_form( $contact_forms[0] ); + } +} + + +/** + * Wrapper function of WPCF7_ContactForm::get_current(). + * + * @return WPCF7_ContactForm Contact form object. + */ +function wpcf7_get_current_contact_form() { + if ( $current = WPCF7_ContactForm::get_current() ) { + return $current; + } +} + + +/** + * Returns true if it is in the state that a non-Ajax submission is accepted. + */ +function wpcf7_is_posted() { + if ( ! $contact_form = wpcf7_get_current_contact_form() ) { + return false; + } + + return $contact_form->is_posted(); +} + + +/** + * Retrieves the user input value through a non-Ajax submission. + * + * @param string $name Name of form control. + * @param string $default_value Optional default value. + * @return string The user input value through the form-control. + */ +function wpcf7_get_hangover( $name, $default_value = null ) { + if ( ! wpcf7_is_posted() ) { + return $default_value; + } + + $submission = WPCF7_Submission::get_instance(); + + if ( ! $submission or $submission->is( 'mail_sent' ) ) { + return $default_value; + } + + return wpcf7_superglobal_post( $name, $default_value ); +} + + +/** + * Retrieves an HTML snippet of validation error on the given form control. + * + * @param string $name Name of form control. + * @return string Validation error message in a form of HTML snippet. + */ +function wpcf7_get_validation_error( $name ) { + if ( ! $contact_form = wpcf7_get_current_contact_form() ) { + return ''; + } + + return $contact_form->validation_error( $name ); +} + + +/** + * Returns a reference key to a validation error message. + * + * @param string $name Name of form control. + * @param string $unit_tag Optional. Unit tag of the contact form. + * @return string Reference key code. + */ +function wpcf7_get_validation_error_reference( $name, $unit_tag = '' ) { + if ( '' === $unit_tag ) { + $contact_form = wpcf7_get_current_contact_form(); + + if ( $contact_form and $contact_form->validation_error( $name ) ) { + $unit_tag = $contact_form->unit_tag(); + } else { + return null; + } + } + + return preg_replace( '/[^0-9a-z_-]+/i', '', + sprintf( + '%1$s-ve-%2$s', + $unit_tag, + $name + ) + ); +} + + +/** + * Retrieves a message for the given status. + */ +function wpcf7_get_message( $status ) { + if ( ! $contact_form = wpcf7_get_current_contact_form() ) { + return ''; + } + + return $contact_form->message( $status ); +} + + +/** + * Returns a class names list for a form-tag of the specified type. + * + * @param string $type Form-tag type. + * @param string $default_classes Optional default classes. + * @return string Whitespace-separated list of class names. + */ +function wpcf7_form_controls_class( $type, $default_classes = '' ) { + $type = trim( $type ); + + if ( is_string( $default_classes ) ) { + $default_classes = explode( ' ', $default_classes ); + } + + $classes = array( + 'wpcf7-form-control', + sprintf( 'wpcf7-%s', rtrim( $type, '*' ) ), + ); + + if ( str_ends_with( $type, '*' ) ) { + $classes[] = 'wpcf7-validates-as-required'; + } + + $classes = array_merge( $classes, $default_classes ); + $classes = array_filter( array_unique( $classes ) ); + + return implode( ' ', $classes ); +} + + +/** + * Callback function for the contact-form-7 shortcode. + */ +function wpcf7_contact_form_tag_func( $atts, $content = null, $code = '' ) { + if ( is_feed() ) { + return '[contact-form-7]'; + } + + if ( 'contact-form-7' === $code ) { + $atts = shortcode_atts( + array( + 'id' => '', + 'title' => '', + 'html_id' => '', + 'html_name' => '', + 'html_title' => '', + 'html_class' => '', + 'output' => 'form', + ), + $atts, 'wpcf7' + ); + + $id = trim( $atts['id'] ); + $title = trim( $atts['title'] ); + + $contact_form = wpcf7_get_contact_form_by_hash( $id ); + + if ( ! $contact_form ) { + $contact_form = wpcf7_contact_form( $id ); + } + + if ( ! $contact_form ) { + $contact_form = wpcf7_get_contact_form_by_title( $title ); + } + + } else { + if ( is_string( $atts ) ) { + $atts = explode( ' ', $atts, 2 ); + } + + $id = (int) array_shift( $atts ); + $contact_form = wpcf7_get_contact_form_by_old_id( $id ); + } + + if ( ! $contact_form ) { + return sprintf( + '

%1$s %2$s

', + esc_html( __( 'Error:', 'contact-form-7' ) ), + esc_html( __( "Contact form not found.", 'contact-form-7' ) ) + ); + } + + $callback = static function ( $contact_form, $atts ) { + return $contact_form->form_html( $atts ); + }; + + $output = wpcf7_switch_locale( + $contact_form->locale(), + $callback, + $contact_form, $atts + ); + + do_action( 'wpcf7_shortcode_callback', $contact_form, $atts ); + + return $output; +} + + +/** + * Saves the contact form data. + */ +function wpcf7_save_contact_form( $data = '', $context = 'save' ) { + $data = wp_parse_args( $data, array( + 'id' => -1, + 'title' => null, + 'locale' => null, + 'form' => null, + 'mail' => null, + 'mail_2' => null, + 'messages' => null, + 'additional_settings' => null, + ) ); + + $data['id'] = (int) $data['id']; + + if ( -1 === $data['id'] ) { + $contact_form = WPCF7_ContactForm::get_template(); + } else { + $contact_form = wpcf7_contact_form( $data['id'] ); + } + + if ( empty( $contact_form ) ) { + return false; + } + + if ( null !== $data['title'] ) { + $contact_form->set_title( $data['title'] ); + } + + if ( null !== $data['locale'] ) { + $contact_form->set_locale( $data['locale'] ); + } + + $properties = array(); + + if ( null !== $data['form'] ) { + $properties['form'] = wpcf7_sanitize_form( $data['form'] ); + } + + if ( null !== $data['mail'] ) { + $properties['mail'] = wpcf7_sanitize_mail( $data['mail'] ); + $properties['mail']['active'] = true; + } + + if ( null !== $data['mail_2'] ) { + $properties['mail_2'] = wpcf7_sanitize_mail( $data['mail_2'] ); + } + + if ( null !== $data['messages'] ) { + $properties['messages'] = wpcf7_sanitize_messages( $data['messages'] ); + } + + if ( null !== $data['additional_settings'] ) { + $properties['additional_settings'] = wpcf7_sanitize_additional_settings( + $data['additional_settings'] + ); + } + + $contact_form->set_properties( $properties ); + + do_action( 'wpcf7_save_contact_form', $contact_form, $data, $context ); + + if ( 'save' === $context ) { + $contact_form->save(); + } + + return $contact_form; +} + + +/** + * Sanitizes the form property data. + */ +function wpcf7_sanitize_form( $input, $default_template = '' ) { + if ( null === $input ) { + return $default_template; + } + + $output = trim( $input ); + + if ( ! current_user_can( 'unfiltered_html' ) ) { + $output = wpcf7_kses( $output, 'form' ); + } + + return $output; +} + + +/** + * Sanitizes the mail property data. + */ +function wpcf7_sanitize_mail( $input, $defaults = array() ) { + $input = wp_parse_args( $input, array( + 'active' => false, + 'subject' => '', + 'sender' => '', + 'recipient' => '', + 'body' => '', + 'additional_headers' => '', + 'attachments' => '', + 'use_html' => false, + 'exclude_blank' => false, + ) ); + + $input = wp_parse_args( $input, $defaults ); + + $output = array(); + $output['active'] = (bool) $input['active']; + $output['subject'] = trim( $input['subject'] ); + $output['sender'] = trim( $input['sender'] ); + $output['recipient'] = trim( $input['recipient'] ); + $output['body'] = trim( $input['body'] ); + + if ( ! current_user_can( 'unfiltered_html' ) ) { + $output['body'] = wpcf7_kses( $output['body'], 'mail' ); + } + + $output['additional_headers'] = ''; + + $headers = str_replace( "\r\n", "\n", $input['additional_headers'] ); + $headers = explode( "\n", $headers ); + + foreach ( $headers as $header ) { + $header = trim( $header ); + + if ( '' !== $header ) { + $output['additional_headers'] .= $header . "\n"; + } + } + + $output['additional_headers'] = trim( $output['additional_headers'] ); + $output['attachments'] = trim( $input['attachments'] ); + $output['use_html'] = (bool) $input['use_html']; + $output['exclude_blank'] = (bool) $input['exclude_blank']; + + return $output; +} + + +/** + * Sanitizes the messages property data. + */ +function wpcf7_sanitize_messages( $input, $defaults = array() ) { + $output = array(); + + foreach ( wpcf7_messages() as $key => $val ) { + if ( isset( $input[$key] ) ) { + $output[$key] = trim( $input[$key] ); + } elseif ( isset( $defaults[$key] ) ) { + $output[$key] = $defaults[$key]; + } + } + + return $output; +} + + +/** + * Sanitizes the additional settings property data. + */ +function wpcf7_sanitize_additional_settings( $input, $default_template = '' ) { + if ( null === $input ) { + return $default_template; + } + + $output = trim( $input ); + return $output; +} + + +/** + * Generates a random hash string for a contact form. + * + * @param int $post_id Post ID. + * @return string SHA-1 hash. + */ +function wpcf7_generate_contact_form_hash( $post_id ) { + return hash( 'sha256', implode( '|', array( + get_current_user_id(), + $post_id, + time(), + home_url(), + ) ) ); +} diff --git a/wp-content/plugins/contact-form-7/includes/contact-form-template.php b/wp-content/plugins/contact-form-7/includes/contact-form-template.php new file mode 100644 index 0000000..528e17f --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/contact-form-template.php @@ -0,0 +1,204 @@ + %2$s + [text* your-name autocomplete:name] + + + + + + + +[submit "%6$s"]', + __( '(optional)', 'contact-form-7' ), + __( 'Your name', 'contact-form-7' ), + __( 'Your email', 'contact-form-7' ), + __( 'Subject', 'contact-form-7' ), + __( 'Your message', 'contact-form-7' ), + __( 'Submit', 'contact-form-7' ) + ); + + return trim( $template ); + } + + public static function mail() { + $template = array( + 'subject' => sprintf( + /* translators: 1: blog name, 2: [your-subject] */ + _x( '%1$s "%2$s"', 'mail subject', 'contact-form-7' ), + '[_site_title]', + '[your-subject]' + ), + 'sender' => sprintf( + '%s <%s>', + '[_site_title]', + self::from_email() + ), + 'body' => + sprintf( + /* translators: %s: [your-name] [your-email] */ + __( 'From: %s', 'contact-form-7' ), + '[your-name] [your-email]' + ) . "\n" + . sprintf( + /* translators: %s: [your-subject] */ + __( 'Subject: %s', 'contact-form-7' ), + '[your-subject]' + ) . "\n\n" + . __( 'Message Body:', 'contact-form-7' ) + . "\n" . '[your-message]' . "\n\n" + . '-- ' . "\n" + . sprintf( + /* translators: 1: blog name, 2: blog URL */ + __( 'This is a notification that a contact form was submitted on your website (%1$s %2$s).', 'contact-form-7' ), + '[_site_title]', + '[_site_url]' + ), + 'recipient' => '[_site_admin_email]', + 'additional_headers' => 'Reply-To: [your-email]', + 'attachments' => '', + 'use_html' => 0, + 'exclude_blank' => 0, + ); + + return $template; + } + + public static function mail_2() { + $template = array( + 'active' => false, + 'subject' => sprintf( + /* translators: 1: blog name, 2: [your-subject] */ + _x( '%1$s "%2$s"', 'mail subject', 'contact-form-7' ), + '[_site_title]', + '[your-subject]' + ), + 'sender' => sprintf( + '%s <%s>', + '[_site_title]', + self::from_email() + ), + 'body' => + __( 'Message Body:', 'contact-form-7' ) + . "\n" . '[your-message]' . "\n\n" + . '-- ' . "\n" + . sprintf( + /* translators: 1: blog name, 2: blog URL */ + __( 'This email is a receipt for your contact form submission on our website (%1$s %2$s) in which your email address was used. If that was not you, please ignore this message.', 'contact-form-7' ), + '[_site_title]', + '[_site_url]' + ), + 'recipient' => '[your-email]', + 'additional_headers' => sprintf( + 'Reply-To: %s', + '[_site_admin_email]' + ), + 'attachments' => '', + 'use_html' => 0, + 'exclude_blank' => 0, + ); + + return $template; + } + + public static function from_email() { + $admin_email = get_option( 'admin_email' ); + + if ( wpcf7_is_localhost() ) { + return $admin_email; + } + + $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST ); + $sitename = strtolower( $sitename ); + + if ( 'www.' === substr( $sitename, 0, 4 ) ) { + $sitename = substr( $sitename, 4 ); + } + + if ( strpbrk( $admin_email, '@' ) === '@' . $sitename ) { + return $admin_email; + } + + return 'wordpress@' . $sitename; + } + + public static function messages() { + $messages = array(); + + foreach ( wpcf7_messages() as $key => $arr ) { + $messages[$key] = $arr['default']; + } + + return $messages; + } +} + +function wpcf7_messages() { + $messages = array( + 'mail_sent_ok' => array( + 'description' => __( 'Sender’s message was sent successfully', 'contact-form-7' ), + 'default' => __( 'Thank you for your message. It has been sent.', 'contact-form-7' ), + ), + + 'mail_sent_ng' => array( + 'description' => __( 'Sender’s message failed to send', 'contact-form-7' ), + 'default' => __( 'There was an error trying to send your message. Please try again later.', 'contact-form-7' ), + ), + + 'validation_error' => array( + 'description' => __( 'Validation errors occurred', 'contact-form-7' ), + 'default' => __( 'One or more fields have an error. Please check and try again.', 'contact-form-7' ), + ), + + 'spam' => array( + 'description' => __( 'Submission was referred to as spam', 'contact-form-7' ), + 'default' => __( 'There was an error trying to send your message. Please try again later.', 'contact-form-7' ), + ), + + 'accept_terms' => array( + 'description' => __( 'There are terms that the sender must accept', 'contact-form-7' ), + 'default' => __( 'You must accept the terms and conditions before sending your message.', 'contact-form-7' ), + ), + + 'invalid_required' => array( + 'description' => __( 'There is a field that the sender must fill in', 'contact-form-7' ), + 'default' => __( 'Please fill out this field.', 'contact-form-7' ), + ), + + 'invalid_too_long' => array( + 'description' => __( 'There is a field with input that is longer than the maximum allowed length', 'contact-form-7' ), + 'default' => __( 'This field has a too long input.', 'contact-form-7' ), + ), + + 'invalid_too_short' => array( + 'description' => __( 'There is a field with input that is shorter than the minimum allowed length', 'contact-form-7' ), + 'default' => __( 'This field has a too short input.', 'contact-form-7' ), + ), + ); + + return apply_filters( 'wpcf7_messages', $messages ); +} diff --git a/wp-content/plugins/contact-form-7/includes/contact-form.php b/wp-content/plugins/contact-form-7/includes/contact-form.php new file mode 100644 index 0000000..cdbb6d6 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/contact-form.php @@ -0,0 +1,1379 @@ + array( + 'name' => __( 'Contact Forms', 'contact-form-7' ), + 'singular_name' => __( 'Contact Form', 'contact-form-7' ), + ), + 'rewrite' => false, + 'query_var' => false, + 'public' => false, + 'capability_type' => 'page', + 'capabilities' => array( + 'edit_post' => 'wpcf7_edit_contact_form', + 'read_post' => 'wpcf7_read_contact_form', + 'delete_post' => 'wpcf7_delete_contact_form', + 'edit_posts' => 'wpcf7_edit_contact_forms', + 'edit_others_posts' => 'wpcf7_edit_contact_forms', + 'publish_posts' => 'wpcf7_edit_contact_forms', + 'read_private_posts' => 'wpcf7_edit_contact_forms', + ), + ) ); + } + + + /** + * Retrieves contact form data that match given conditions. + * + * @param string|array $args Optional. Arguments to be passed to WP_Query. + * @return array Array of WPCF7_ContactForm objects. + */ + public static function find( $args = '' ) { + $defaults = array( + 'post_status' => 'any', + 'posts_per_page' => -1, + 'offset' => 0, + 'orderby' => 'ID', + 'order' => 'ASC', + ); + + $args = wp_parse_args( $args, $defaults ); + + $args['post_type'] = self::post_type; + + $q = new WP_Query(); + $posts = $q->query( $args ); + + self::$found_items = $q->found_posts; + + $objs = array(); + + foreach ( $posts as $post ) { + $objs[] = new self( $post ); + } + + return $objs; + } + + + /** + * Returns a contact form data filled by default template contents. + * + * @param string|array $options Optional. Contact form options. + * @return WPCF7_ContactForm A new contact form object. + */ + public static function get_template( $options = '' ) { + $options = wp_parse_args( $options, array( + 'locale' => null, + 'title' => __( 'Untitled', 'contact-form-7' ), + ) ); + + if ( ! isset( $options['locale'] ) ) { + $options['locale'] = determine_locale(); + } + + $callback = static function ( $options ) { + $contact_form = new self(); + $contact_form->title = $options['title']; + $contact_form->locale = $options['locale']; + + $properties = $contact_form->get_properties(); + + foreach ( $properties as $key => $value ) { + $default_template = WPCF7_ContactFormTemplate::get_default( $key ); + + if ( isset( $default_template ) ) { + $properties[$key] = $default_template; + } + } + + $contact_form->properties = $properties; + + return $contact_form; + }; + + $contact_form = wpcf7_switch_locale( + $options['locale'], + $callback, + $options + ); + + self::$current = apply_filters( 'wpcf7_contact_form_default_pack', + $contact_form, $options + ); + + return self::$current; + } + + + /** + * Creates a WPCF7_ContactForm object and sets it as the current instance. + * + * @param WPCF7_ContactForm|WP_Post|int $post Object or post ID. + * @return WPCF7_ContactForm|null Contact form object. Null if unset. + */ + public static function get_instance( $post ) { + $contact_form = null; + + if ( $post instanceof self ) { + $contact_form = $post; + } elseif ( ! empty( $post ) ) { + $post = get_post( $post ); + + if ( isset( $post ) and self::post_type === get_post_type( $post ) ) { + $contact_form = new self( $post ); + } + } + + return self::$current = $contact_form; + } + + + /** + * Generates a "unit-tag" for the given contact form ID. + * + * @return string Unit-tag. + */ + private static function generate_unit_tag( $id = 0 ) { + static $global_count = 0; + + $global_count += 1; + + if ( in_the_loop() ) { + $unit_tag = sprintf( 'wpcf7-f%1$d-p%2$d-o%3$d', + absint( $id ), + get_the_ID(), + $global_count + ); + } else { + $unit_tag = sprintf( 'wpcf7-f%1$d-o%2$d', + absint( $id ), + $global_count + ); + } + + return $unit_tag; + } + + + /** + * Constructor. + */ + private function __construct( $post = null ) { + $post = get_post( $post ); + + if ( $post and self::post_type === get_post_type( $post ) ) { + $this->id = $post->ID; + $this->name = $post->post_name; + $this->title = $post->post_title; + $this->locale = get_post_meta( $post->ID, '_locale', true ); + $this->hash = get_post_meta( $post->ID, '_hash', true ); + + $this->construct_properties( $post ); + $this->upgrade(); + } else { + $this->construct_properties(); + } + + do_action( 'wpcf7_contact_form', $this ); + } + + + /** + * Magic method for property overloading. + */ + public function __get( $name ) { + /* translators: 1: property name, 2: method name */ + $message = __( '%1$s property of a WPCF7_ContactForm object is no longer accessible. Use %2$s method instead.', 'contact-form-7' ); + + if ( 'id' === $name ) { + wp_trigger_error( + '', + sprintf( $message, 'id', 'id()' ), + E_USER_DEPRECATED + ); + + return $this->id; + } elseif ( 'title' === $name ) { + wp_trigger_error( + '', + sprintf( $message, 'title', 'title()' ), + E_USER_DEPRECATED + ); + + return $this->title; + } elseif ( $prop = $this->prop( $name ) ) { + wp_trigger_error( + '', + sprintf( $message, $name, 'prop(\'' . $name . '\')' ), + E_USER_DEPRECATED + ); + + return $prop; + } + } + + + /** + * Returns true if this contact form is not yet saved to the database. + */ + public function initial() { + return empty( $this->id ); + } + + + /** + * Constructs contact form properties. This is called only once + * from the constructor. + */ + private function construct_properties( $post = null ) { + $builtin_properties = array( + 'form' => '', + 'mail' => array(), + 'mail_2' => array(), + 'messages' => array(), + 'additional_settings' => '', + ); + + $properties = apply_filters( + 'wpcf7_pre_construct_contact_form_properties', + $builtin_properties, $this + ); + + // Filtering out properties with invalid name + $properties = array_filter( + $properties, + static function ( $key ) { + $sanitized_key = sanitize_key( $key ); + return $key === $sanitized_key; + }, + ARRAY_FILTER_USE_KEY + ); + + foreach ( $properties as $name => $val ) { + $prop = $this->retrieve_property( $name ); + + if ( isset( $prop ) ) { + $properties[$name] = $prop; + } + } + + $this->properties = $properties; + + foreach ( $properties as $name => $val ) { + $properties[$name] = apply_filters( + "wpcf7_contact_form_property_{$name}", + $val, $this + ); + } + + $this->properties = $properties; + + $properties = (array) apply_filters( + 'wpcf7_contact_form_properties', + $properties, $this + ); + + $this->properties = $properties; + } + + + /** + * Retrieves contact form property of the specified name from the database. + * + * @param string $name Property name. + * @return array|string|null Property value. Null if property does not exist. + */ + private function retrieve_property( $name ) { + $property = null; + + if ( ! $this->initial() ) { + $post_id = $this->id; + + if ( metadata_exists( 'post', $post_id, '_' . $name ) ) { + $property = get_post_meta( $post_id, '_' . $name, true ); + } elseif ( metadata_exists( 'post', $post_id, $name ) ) { + $property = get_post_meta( $post_id, $name, true ); + } + } + + return $property; + } + + + /** + * Returns the value for the given property name. + * + * @param string $name Property name. + * @return array|string|null Property value. Null if property does not exist. + */ + public function prop( $name ) { + $props = $this->get_properties(); + return isset( $props[$name] ) ? $props[$name] : null; + } + + + /** + * Returns all the properties. + * + * @return array This contact form's properties. + */ + public function get_properties() { + return (array) $this->properties; + } + + + /** + * Updates properties. + * + * @param array $properties New properties. + */ + public function set_properties( $properties ) { + $defaults = $this->get_properties(); + + $properties = wp_parse_args( $properties, $defaults ); + $properties = array_intersect_key( $properties, $defaults ); + + $this->properties = $properties; + } + + + /** + * Returns ID of this contact form. + * + * @return int The ID. + */ + public function id() { + return $this->id; + } + + + /** + * Returns unit-tag for this contact form. + * + * @return string Unit-tag. + */ + public function unit_tag() { + return $this->unit_tag; + } + + + /** + * Returns name (slug) of this contact form. + * + * @return string Name. + */ + public function name() { + return $this->name; + } + + + /** + * Returns title of this contact form. + * + * @return string Title. + */ + public function title() { + return $this->title; + } + + + /** + * Set a title for this contact form. + * + * @param string $title Title. + */ + public function set_title( $title ) { + $title = wp_strip_all_tags( $title, true ); + $title = wpcf7_strip_whitespaces( $title ); + + if ( '' === $title ) { + $title = __( 'Untitled', 'contact-form-7' ); + } + + $this->title = $title; + } + + + /** + * Returns the locale code of this contact form. + * + * @return string Locale code. Empty string if no valid locale is set. + */ + public function locale() { + if ( wpcf7_is_valid_locale( $this->locale ) ) { + return $this->locale; + } else { + return ''; + } + } + + + /** + * Sets a locale for this contact form. + * + * @param string $locale Locale code. + */ + public function set_locale( $locale ) { + $locale = trim( $locale ); + + if ( wpcf7_is_valid_locale( $locale ) ) { + $this->locale = $locale; + } else { + $this->locale = 'en_US'; + } + } + + + /** + * Retrieves the random hash string tied to this contact form. + * + * @param int $length Length of hash string. + * @return string Hash string unique to this contact form. + */ + public function hash( $length = 7 ) { + return substr( $this->hash, 0, absint( $length ) ); + } + + + /** + * Returns the specified shortcode attribute value. + * + * @param string $name Shortcode attribute name. + * @return string|null Attribute value. Null if the attribute does not exist. + */ + public function shortcode_attr( $name ) { + if ( isset( $this->shortcode_atts[$name] ) ) { + return (string) $this->shortcode_atts[$name]; + } + } + + + /** + * Returns true if this contact form is identical to the submitted one. + */ + public function is_posted() { + if ( ! WPCF7_Submission::get_instance() ) { + return false; + } + + $unit_tag = wpcf7_superglobal_post( '_wpcf7_unit_tag' ); + + if ( empty( $unit_tag ) ) { + return false; + } + + return $this->unit_tag() === $unit_tag; + } + + + /** + * Generates HTML that represents a form. + * + * @param string|array $options Optional. Form options. + * @return string HTML output. + */ + public function form_html( $options = '' ) { + $options = wp_parse_args( $options, array( + 'html_id' => '', + 'html_name' => '', + 'html_title' => '', + 'html_class' => '', + 'output' => 'form', + ) ); + + $this->shortcode_atts = $options; + + if ( 'raw_form' === $options['output'] ) { + return sprintf( + '
%s
', + esc_html( $this->prop( 'form' ) ) + ); + } + + if ( + $this->is_true( 'subscribers_only' ) and + ! current_user_can( 'wpcf7_submit', $this->id() ) + ) { + $notice = sprintf( + '

%s

', + wp_kses_data( __( 'This contact form is available only for logged in users.', 'contact-form-7' ) ) + ); + + return apply_filters( 'wpcf7_subscribers_only_notice', $notice, $this ); + } + + $this->unit_tag = self::generate_unit_tag( $this->id ); + + $action_url = wpcf7_get_request_uri(); + + if ( $frag = strstr( $action_url, '#' ) ) { + $action_url = substr( $action_url, 0, -strlen( $frag ) ); + } + + $action_url .= '#' . $this->unit_tag(); + + $action_url = apply_filters( 'wpcf7_form_action_url', $action_url ); + + if ( + str_starts_with( $action_url, '//' ) or + ! str_starts_with( $action_url, '/' ) and + ! str_starts_with( $action_url, home_url() ) + ) { + return sprintf( + '

%1$s %2$s

', + esc_html( __( 'Error:', 'contact-form-7' ) ), + esc_html( __( 'Invalid action URL is detected.', 'contact-form-7' ) ) + ); + } + + $lang_tag = str_replace( '_', '-', $this->locale ); + + if ( preg_match( '/^([a-z]+-[a-z]+)-/i', $lang_tag, $matches ) ) { + $lang_tag = $matches[1]; + } + + $html = "\n" . sprintf( '
', + wpcf7_format_atts( array( + 'class' => 'wpcf7 no-js', + 'id' => $this->unit_tag(), + ( get_option( 'html_type' ) === 'text/html' ) ? 'lang' : 'xml:lang' + => $lang_tag, + 'dir' => wpcf7_is_rtl( $this->locale ) ? 'rtl' : 'ltr', + 'data-wpcf7-id' => $this->id(), + ) ) + ); + + $html .= "\n" . $this->screen_reader_response() . "\n"; + + $id_attr = apply_filters( 'wpcf7_form_id_attr', + preg_replace( '/[^A-Za-z0-9:._-]/', '', $options['html_id'] ) + ); + + $name_attr = apply_filters( 'wpcf7_form_name_attr', + preg_replace( '/[^A-Za-z0-9:._-]/', '', $options['html_name'] ) + ); + + $title_attr = apply_filters( 'wpcf7_form_title_attr', $options['html_title'] ); + + $class = 'wpcf7-form'; + + if ( $this->is_posted() ) { + $submission = WPCF7_Submission::get_instance(); + + $data_status_attr = $this->form_status_class_name( + $submission->get_status() + ); + + $class .= sprintf( ' %s', $data_status_attr ); + } else { + $data_status_attr = 'init'; + $class .= ' init'; + } + + if ( $options['html_class'] ) { + $class .= ' ' . $options['html_class']; + } + + if ( $this->in_demo_mode() ) { + $class .= ' demo'; + } + + $class = explode( ' ', $class ); + $class = array_map( 'sanitize_html_class', $class ); + $class = array_filter( $class ); + $class = array_unique( $class ); + $class = implode( ' ', $class ); + $class = apply_filters( 'wpcf7_form_class_attr', $class ); + + $enctype = wpcf7_enctype_value( apply_filters( 'wpcf7_form_enctype', '' ) ); + $autocomplete = apply_filters( 'wpcf7_form_autocomplete', '' ); + + $atts = array( + 'action' => esc_url( $action_url ), + 'method' => 'post', + 'class' => ( '' !== $class ) ? $class : null, + 'id' => ( '' !== $id_attr ) ? $id_attr : null, + 'name' => ( '' !== $name_attr ) ? $name_attr : null, + 'aria-label' => ( '' !== $title_attr ) + ? $title_attr : __( 'Contact form', 'contact-form-7' ), + 'enctype' => ( '' !== $enctype ) ? $enctype : null, + 'autocomplete' => ( '' !== $autocomplete ) ? $autocomplete : null, + 'novalidate' => true, + 'data-status' => $data_status_attr, + ); + + $atts += (array) apply_filters( 'wpcf7_form_additional_atts', array() ); + + $html .= sprintf( '
', wpcf7_format_atts( $atts ) ) . "\n"; + $html .= $this->form_hidden_fields(); + $html .= $this->form_elements(); + + if ( ! $this->responses_count ) { + $html .= $this->form_response_output(); + } + + $html .= "\n" . '
'; + $html .= "\n" . '
'; + + return $html . "\n"; + } + + + /** + * Returns the class name that matches the given form status. + */ + private function form_status_class_name( $status ) { + switch ( $status ) { + case 'init': + $class = 'init'; + break; + case 'validation_failed': + $class = 'invalid'; + break; + case 'acceptance_missing': + $class = 'unaccepted'; + break; + case 'spam': + $class = 'spam'; + break; + case 'aborted': + $class = 'aborted'; + break; + case 'mail_sent': + $class = 'sent'; + break; + case 'mail_failed': + $class = 'failed'; + break; + default: + $class = sprintf( + 'custom-%s', + preg_replace( '/[^0-9a-z]+/i', '-', $status ) + ); + } + + return $class; + } + + + /** + * Returns a set of hidden fields. + */ + private function form_hidden_fields() { + $hidden_fields = array( + '_wpcf7' => $this->id(), + '_wpcf7_version' => WPCF7_VERSION, + '_wpcf7_locale' => $this->locale(), + '_wpcf7_unit_tag' => $this->unit_tag(), + '_wpcf7_container_post' => 0, + '_wpcf7_posted_data_hash' => '', + ); + + if ( in_the_loop() ) { + $hidden_fields['_wpcf7_container_post'] = (int) get_the_ID(); + } + + if ( $this->nonce_is_active() and is_user_logged_in() ) { + $hidden_fields['_wpnonce'] = wpcf7_create_nonce(); + } + + $hidden_fields += (array) apply_filters( + 'wpcf7_form_hidden_fields', array() + ); + + $formatter = new WPCF7_HTMLFormatter(); + + $formatter->append_start_tag( 'fieldset', array( + 'class' => 'hidden-fields-container', + ) ); + + foreach ( $hidden_fields as $name => $value ) { + $formatter->append_start_tag( 'input', array( + 'type' => 'hidden', + 'name' => $name, + 'value' => $value, + ) ); + } + + return $formatter->output() . "\n"; + } + + + /** + * Returns the visible response output for a form submission. + */ + public function form_response_output() { + $status = 'init'; + $class = 'wpcf7-response-output'; + $content = ''; + + if ( $this->is_posted() ) { // Post response output for non-AJAX + $submission = WPCF7_Submission::get_instance(); + $status = $submission->get_status(); + $content = $submission->get_response(); + } + + $atts = array( + 'class' => trim( $class ), + 'aria-hidden' => 'true', + ); + + $output = sprintf( '
%2$s
', + wpcf7_format_atts( $atts ), + esc_html( $content ) + ); + + $output = apply_filters( 'wpcf7_form_response_output', + $output, $class, $content, $this, $status + ); + + $this->responses_count += 1; + + return $output; + } + + + /** + * Returns the response output that is only accessible from screen readers. + */ + public function screen_reader_response() { + $primary_response = ''; + $validation_errors = array(); + + if ( $this->is_posted() ) { // Post response output for non-AJAX + $submission = WPCF7_Submission::get_instance(); + $primary_response = $submission->get_response(); + + if ( $invalid_fields = $submission->get_invalid_fields() ) { + foreach ( (array) $invalid_fields as $name => $field ) { + $list_item = esc_html( $field['reason'] ); + + if ( $field['idref'] ) { + $list_item = sprintf( + '%2$s', + esc_attr( $field['idref'] ), + $list_item + ); + } + + $validation_error_id = wpcf7_get_validation_error_reference( + $name, + $this->unit_tag() + ); + + if ( $validation_error_id ) { + $list_item = sprintf( + '
  • %2$s
  • ', + esc_attr( $validation_error_id ), + $list_item + ); + + $validation_errors[] = $list_item; + } + } + } + } + + $primary_response = sprintf( + '

    %s

    ', + esc_html( $primary_response ) + ); + + $validation_errors = sprintf( + '
      %s
    ', + implode( "\n", $validation_errors ) + ); + + $output = sprintf( + '
    %1$s %2$s
    ', + $primary_response, + $validation_errors + ); + + return $output; + } + + + /** + * Returns a validation error for the specified input field. + * + * @param string $name Input field name. + */ + public function validation_error( $name ) { + $error = ''; + + if ( $this->is_posted() ) { + $submission = WPCF7_Submission::get_instance(); + + if ( $invalid_field = $submission->get_invalid_field( $name ) ) { + $error = trim( $invalid_field['reason'] ); + } + } + + if ( ! $error ) { + return $error; + } + + $atts = array( + 'class' => 'wpcf7-not-valid-tip', + 'aria-hidden' => 'true', + ); + + $error = sprintf( + '%2$s', + wpcf7_format_atts( $atts ), + esc_html( $error ) + ); + + return apply_filters( 'wpcf7_validation_error', $error, $name, $this ); + } + + + /** + * Replaces all form-tags in the form template with corresponding HTML. + * + * @return string Replaced form content. + */ + public function replace_all_form_tags() { + $manager = WPCF7_FormTagsManager::get_instance(); + $form = $this->prop( 'form' ); + + if ( wpcf7_autop_or_not() ) { + $form = $manager->replace_with_placeholders( $form ); + $form = wpcf7_autop( $form ); + $form = $manager->restore_from_placeholders( $form ); + } + + $form = $manager->replace_all( $form ); + $this->scanned_form_tags = $manager->get_scanned_tags(); + + return $form; + } + + + /** + * Replaces all form-tags in the form template with corresponding HTML. + * + * @deprecated 4.6 Use replace_all_form_tags() + * + * @return string Replaced form content. + */ + public function form_do_shortcode() { + wpcf7_deprecated_function( __METHOD__, '4.6', + 'WPCF7_ContactForm::replace_all_form_tags' + ); + + return $this->replace_all_form_tags(); + } + + + /** + * Scans form-tags from the form template. + * + * @param string|array|null $cond Optional. Filters. Default null. + * @return array Form-tags matching the given filter conditions. + */ + public function scan_form_tags( $cond = null ) { + $manager = WPCF7_FormTagsManager::get_instance(); + + if ( empty( $this->scanned_form_tags ) ) { + $this->scanned_form_tags = $manager->scan( $this->prop( 'form' ) ); + } + + $tags = $this->scanned_form_tags; + + return $manager->filter( $tags, $cond ); + } + + + /** + * Scans form-tags from the form template. + * + * @deprecated 4.6 Use scan_form_tags() + * + * @param string|array|null $cond Optional. Filters. Default null. + * @return array Form-tags matching the given filter conditions. + */ + public function form_scan_shortcode( $cond = null ) { + wpcf7_deprecated_function( __METHOD__, '4.6', + 'WPCF7_ContactForm::scan_form_tags' + ); + + return $this->scan_form_tags( $cond ); + } + + + /** + * Replaces all form-tags in the form template with corresponding HTML. + * + * @return string Replaced form content. wpcf7_form_elements filters applied. + */ + public function form_elements() { + return apply_filters( 'wpcf7_form_elements', + $this->replace_all_form_tags() + ); + } + + + /** + * Collects mail-tags available for this contact form. + * + * @param string|array $options Optional. Search options. + * @return array Mail-tag names. + */ + public function collect_mail_tags( $options = '' ) { + $manager = WPCF7_FormTagsManager::get_instance(); + + $options = wp_parse_args( $options, array( + 'include' => array(), + 'exclude' => $manager->collect_tag_types( 'not-for-mail' ), + ) ); + + $tags = $this->scan_form_tags(); + $mailtags = array(); + + foreach ( (array) $tags as $tag ) { + $type = $tag->basetype; + + if ( empty( $type ) ) { + continue; + } elseif ( ! empty( $options['include'] ) ) { + if ( ! in_array( $type, $options['include'], true ) ) { + continue; + } + } elseif ( ! empty( $options['exclude'] ) ) { + if ( in_array( $type, $options['exclude'], true ) ) { + continue; + } + } + + $mailtags[] = $tag->name; + } + + $mailtags = array_unique( $mailtags ); + $mailtags = array_filter( $mailtags ); + $mailtags = array_values( $mailtags ); + + return apply_filters( 'wpcf7_collect_mail_tags', $mailtags, $options, $this ); + } + + + /** + * Prints a mail-tag suggestion list. + * + * @param string $template_name Optional. Mail template name. Default 'mail'. + */ + public function suggest_mail_tags( $template_name = 'mail' ) { + $mail = wp_parse_args( $this->prop( $template_name ), + array( + 'active' => false, + 'recipient' => '', + 'sender' => '', + 'subject' => '', + 'body' => '', + 'additional_headers' => '', + 'attachments' => '', + 'use_html' => false, + 'exclude_blank' => false, + ) + ); + + $mail = array_filter( $mail ); + + foreach ( (array) $this->collect_mail_tags() as $mail_tag ) { + $pattern = sprintf( + '/\[(_[a-z]+_)?%s([ \t]+[^]]+)?\]/', + preg_quote( $mail_tag, '/' ) + ); + + $used = preg_grep( $pattern, $mail ); + + echo sprintf( + '[%2$s]', + 'mailtag code ' . ( $used ? 'used' : 'unused' ), + esc_html( $mail_tag ) + ); + } + } + + + /** + * Submits this contact form. + * + * @param string|array $options Optional. Submission options. Default empty. + * @return array Result of submission. + */ + public function submit( $options = '' ) { + $options = wp_parse_args( $options, array( + 'skip_mail' => ( + $this->in_demo_mode() || + $this->is_true( 'skip_mail' ) || + ! empty( $this->skip_mail ) + ), + ) ); + + if ( + $this->is_true( 'subscribers_only' ) and + ! current_user_can( 'wpcf7_submit', $this->id() ) + ) { + $result = array( + 'contact_form_id' => $this->id(), + 'status' => 'error', + 'message' => __( 'This contact form is available only for logged in users.', 'contact-form-7' ), + ); + + return $result; + } + + $submission = WPCF7_Submission::get_instance( $this, array( + 'skip_mail' => $options['skip_mail'], + ) ); + + $result = array( + 'contact_form_id' => $this->id(), + ); + + $result += $submission->get_result(); + + if ( $this->in_demo_mode() ) { + $result['demo_mode'] = true; + } + + do_action( 'wpcf7_submit', $this, $result ); + + return $result; + } + + + /** + * Returns message used for given status. + * + * @param string $status Status. + * @param bool $filter Optional. Whether filters are applied. Default true. + * @return string Message. + */ + public function message( $status, $filter = true ) { + $messages = $this->prop( 'messages' ); + $message = isset( $messages[$status] ) ? $messages[$status] : ''; + + if ( $filter ) { + $message = $this->filter_message( $message, $status ); + } + + return $message; + } + + + /** + * Filters a message. + * + * @param string $message Message to filter. + * @param string $status Optional. Status. Default empty. + * @return string Filtered message. + */ + public function filter_message( $message, $status = '' ) { + $message = wpcf7_mail_replace_tags( $message ); + $message = apply_filters( 'wpcf7_display_message', $message, $status ); + $message = wp_strip_all_tags( $message ); + + return $message; + } + + + /** + * Returns the additional setting value searched by name. + * + * @param string $name Name of setting. + * @return string Additional setting value. + */ + public function pref( $name ) { + $settings = $this->additional_setting( $name ); + + if ( $settings ) { + return $settings[0]; + } + } + + + /** + * Returns additional setting values searched by name. + * + * @param string $name Name of setting. + * @param int $max Maximum result item count. + * @return array Additional setting values. + */ + public function additional_setting( $name, $max = 1 ) { + $settings = (array) explode( "\n", $this->prop( 'additional_settings' ) ); + + $pattern = '/^([a-zA-Z0-9_]+)[\t ]*:(.*)$/'; + $count = 0; + $values = array(); + + foreach ( $settings as $setting ) { + if ( preg_match( $pattern, $setting, $matches ) ) { + if ( $matches[1] !== $name ) { + continue; + } + + if ( ! $max or $count < (int) $max ) { + $values[] = trim( $matches[2] ); + $count += 1; + } + } + } + + return $values; + } + + + /** + * Returns true if the specified setting has a truthy string value. + * + * @param string $name Name of setting. + * @return bool True if the setting value is 'on', 'true', or '1'. + */ + public function is_true( $name ) { + return in_array( + $this->pref( $name ), + array( 'on', 'true', '1' ), + true + ); + } + + + /** + * Returns true if this contact form is in the demo mode. + */ + public function in_demo_mode() { + return $this->is_true( 'demo_mode' ); + } + + + /** + * Returns true if nonce is active for this contact form. + */ + public function nonce_is_active() { + $is_active = WPCF7_VERIFY_NONCE; + + if ( $this->is_true( 'subscribers_only' ) ) { + $is_active = true; + } + + return (bool) apply_filters( 'wpcf7_verify_nonce', $is_active, $this ); + } + + + /** + * Returns true if the specified setting has a falsey string value. + * + * @param string $name Name of setting. + * @return bool True if the setting value is 'off', 'false', or '0'. + */ + public function is_false( $name ) { + return in_array( + $this->pref( $name ), + array( 'off', 'false', '0' ), + true + ); + } + + + /** + * Upgrades this contact form properties. + */ + private function upgrade() { + $mail = $this->prop( 'mail' ); + + if ( is_array( $mail ) and ! isset( $mail['recipient'] ) ) { + $mail['recipient'] = get_option( 'admin_email' ); + } + + $this->properties['mail'] = $mail; + + $messages = $this->prop( 'messages' ); + + if ( is_array( $messages ) ) { + foreach ( wpcf7_messages() as $key => $arr ) { + if ( ! isset( $messages[$key] ) ) { + $messages[$key] = $arr['default']; + } + } + } + + $this->properties['messages'] = $messages; + } + + + /** + * Stores this contact form properties to the database. + * + * @return int The post ID on success. The value 0 on failure. + */ + public function save() { + $title = wp_slash( $this->title ); + $props = wp_slash( $this->get_properties() ); + + $post_content = implode( "\n", wpcf7_array_flatten( $props ) ); + + if ( $this->initial() ) { + $post_id = wp_insert_post( array( + 'post_type' => self::post_type, + 'post_status' => 'publish', + 'post_title' => $title, + 'post_content' => trim( $post_content ), + ) ); + } else { + $post_id = wp_update_post( array( + 'ID' => (int) $this->id, + 'post_status' => 'publish', + 'post_title' => $title, + 'post_content' => trim( $post_content ), + ) ); + } + + if ( $post_id ) { + foreach ( $props as $prop => $value ) { + update_post_meta( $post_id, '_' . $prop, + wpcf7_normalize_newline_deep( $value ) + ); + } + + if ( wpcf7_is_valid_locale( $this->locale ) ) { + update_post_meta( $post_id, '_locale', $this->locale ); + } + + add_post_meta( $post_id, '_hash', + wpcf7_generate_contact_form_hash( $post_id ), + true // Unique + ); + + if ( $this->initial() ) { + $this->id = $post_id; + do_action( 'wpcf7_after_create', $this ); + } else { + do_action( 'wpcf7_after_update', $this ); + } + + do_action( 'wpcf7_after_save', $this ); + } + + return $post_id; + } + + + /** + * Makes a copy of this contact form. + * + * @return WPCF7_ContactForm New contact form object. + */ + public function copy() { + $new = new self(); + $new->title = $this->title . '_copy'; + $new->locale = $this->locale; + $new->properties = $this->properties; + + return apply_filters( 'wpcf7_copy', $new, $this ); + } + + + /** + * Deletes this contact form. + * + * @return bool True if deletion succeeded, false otherwise. + */ + public function delete() { + if ( $this->initial() ) { + return false; + } + + if ( wp_delete_post( $this->id, true ) ) { + $this->id = 0; + return true; + } + + return false; + } + + + /** + * Returns a WordPress shortcode for this contact form. + */ + public function shortcode( $options = '' ) { + $options = wp_parse_args( $options, array( + 'use_old_format' => false + ) ); + + $title = str_replace( array( '"', '[', ']' ), '', $this->title ); + + if ( $options['use_old_format'] ) { + $old_unit_id = (int) get_post_meta( $this->id, '_old_cf7_unit_id', true ); + + if ( $old_unit_id ) { + $shortcode = sprintf( + '[contact-form %1$d "%2$s"]', + $old_unit_id, + $title + ); + } else { + $shortcode = ''; + } + } else { + $shortcode = sprintf( + '[contact-form-7 id="%1$s" title="%2$s"]', + $this->hash(), + $title + ); + } + + return apply_filters( 'wpcf7_contact_form_shortcode', + $shortcode, $options, $this + ); + } +} diff --git a/wp-content/plugins/contact-form-7/includes/controller.php b/wp-content/plugins/contact-form-7/includes/controller.php new file mode 100644 index 0000000..7d2b876 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/controller.php @@ -0,0 +1,184 @@ +submit(); + } +} + + +/** + * Registers main scripts and styles. + */ +add_action( + 'wp_enqueue_scripts', + static function () { + $assets = include wpcf7_plugin_path( 'includes/js/index.asset.php' ); + + $assets = wp_parse_args( $assets, array( + 'dependencies' => array(), + 'version' => WPCF7_VERSION, + ) ); + + wp_register_script( + 'contact-form-7', + wpcf7_plugin_url( 'includes/js/index.js' ), + array_merge( + $assets['dependencies'], + array( 'swv' ) + ), + $assets['version'], + array( 'in_footer' => true ) + ); + + wp_set_script_translations( 'contact-form-7', 'contact-form-7' ); + + wp_register_script( + 'contact-form-7-html5-fallback', + wpcf7_plugin_url( 'includes/js/html5-fallback.js' ), + array( 'jquery-ui-datepicker' ), + WPCF7_VERSION, + array( 'in_footer' => true ) + ); + + if ( wpcf7_load_js() ) { + wpcf7_enqueue_scripts(); + } + + wp_register_style( + 'contact-form-7', + wpcf7_plugin_url( 'includes/css/styles.css' ), + array(), + WPCF7_VERSION, + 'all' + ); + + wp_register_style( + 'contact-form-7-rtl', + wpcf7_plugin_url( 'includes/css/styles-rtl.css' ), + array( 'contact-form-7' ), + WPCF7_VERSION, + 'all' + ); + + wp_register_style( + 'jquery-ui-smoothness', + wpcf7_plugin_url( + 'includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css' + ), + array(), + '1.12.1', + 'screen' + ); + + if ( wpcf7_load_css() ) { + wpcf7_enqueue_styles(); + } + }, + 10, 0 +); + + +/** + * Enqueues scripts. + */ +function wpcf7_enqueue_scripts() { + wp_enqueue_script( 'contact-form-7' ); + + $wpcf7_obj = array( + 'api' => array( + 'root' => sanitize_url( get_rest_url() ), + 'namespace' => 'contact-form-7/v1', + ), + ); + + if ( defined( 'WP_CACHE' ) and WP_CACHE ) { + $wpcf7_obj = array_merge( $wpcf7_obj, array( + 'cached' => 1, + ) ); + } + + wp_add_inline_script( 'contact-form-7', + sprintf( + 'var wpcf7 = %s;', + wp_json_encode( $wpcf7_obj, JSON_PRETTY_PRINT ) + ), + 'before' + ); + + do_action( 'wpcf7_enqueue_scripts' ); +} + + +/** + * Returns true if the main script is enqueued. + */ +function wpcf7_script_is() { + return wp_script_is( 'contact-form-7' ); +} + + +/** + * Enqueues styles. + */ +function wpcf7_enqueue_styles() { + wp_enqueue_style( 'contact-form-7' ); + + if ( wpcf7_is_rtl() ) { + wp_enqueue_style( 'contact-form-7-rtl' ); + } + + do_action( 'wpcf7_enqueue_styles' ); +} + + +/** + * Returns true if the main stylesheet is enqueued. + */ +function wpcf7_style_is() { + return wp_style_is( 'contact-form-7' ); +} + + +add_action( + 'wp_enqueue_scripts', + 'wpcf7_html5_fallback', + 20, 0 +); + +/** + * Enqueues scripts and styles for the HTML5 fallback. + */ +function wpcf7_html5_fallback() { + if ( ! wpcf7_support_html5_fallback() ) { + return; + } + + if ( wpcf7_script_is() ) { + wp_enqueue_script( 'contact-form-7-html5-fallback' ); + } + + if ( wpcf7_style_is() ) { + wp_enqueue_style( 'jquery-ui-smoothness' ); + } +} diff --git a/wp-content/plugins/contact-form-7/includes/css/styles-rtl.css b/wp-content/plugins/contact-form-7/includes/css/styles-rtl.css new file mode 100644 index 0000000..2ec20fd --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/css/styles-rtl.css @@ -0,0 +1,11 @@ +.wpcf7-not-valid-tip { + direction: rtl; +} + +.use-floating-validation-tip .wpcf7-not-valid-tip { + right: 1em; +} + +.wpcf7-list-item { + margin: 0 1em 0 0; +} diff --git a/wp-content/plugins/contact-form-7/includes/css/styles.css b/wp-content/plugins/contact-form-7/includes/css/styles.css new file mode 100644 index 0000000..e6517e9 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/css/styles.css @@ -0,0 +1,176 @@ +.wpcf7 .screen-reader-response { + position: absolute; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; + word-wrap: normal !important; +} + +.wpcf7 .hidden-fields-container { + display: none; +} + +.wpcf7 form .wpcf7-response-output { + margin: 2em 0.5em 1em; + padding: 0.2em 1em; + border: 2px solid #00a0d2; /* Blue */ +} + +.wpcf7 form.init .wpcf7-response-output, +.wpcf7 form.resetting .wpcf7-response-output, +.wpcf7 form.submitting .wpcf7-response-output { + display: none; +} + +.wpcf7 form.sent .wpcf7-response-output { + border-color: #46b450; /* Green */ +} + +.wpcf7 form.failed .wpcf7-response-output, +.wpcf7 form.aborted .wpcf7-response-output { + border-color: #dc3232; /* Red */ +} + +.wpcf7 form.spam .wpcf7-response-output { + border-color: #f56e28; /* Orange */ +} + +.wpcf7 form.invalid .wpcf7-response-output, +.wpcf7 form.unaccepted .wpcf7-response-output, +.wpcf7 form.payment-required .wpcf7-response-output { + border-color: #ffb900; /* Yellow */ +} + +.wpcf7-form-control-wrap { + position: relative; +} + +.wpcf7-not-valid-tip { + color: #dc3232; /* Red */ + font-size: 1em; + font-weight: normal; + display: block; +} + +.use-floating-validation-tip .wpcf7-not-valid-tip { + position: relative; + top: -2ex; + left: 1em; + z-index: 100; + border: 1px solid #dc3232; + background: #fff; + padding: .2em .8em; + width: 24em; +} + +.wpcf7-list-item { + display: inline-block; + margin: 0 0 0 1em; +} + +.wpcf7-list-item-label::before, +.wpcf7-list-item-label::after { + content: " "; +} + +.wpcf7-spinner { + visibility: hidden; + display: inline-block; + background-color: #23282d; /* Dark Gray 800 */ + opacity: 0.75; + width: 24px; + height: 24px; + border: none; + border-radius: 100%; + padding: 0; + margin: 0 24px; + position: relative; +} + +form.submitting .wpcf7-spinner { + visibility: visible; +} + +.wpcf7-spinner::before { + content: ''; + position: absolute; + background-color: #fbfbfc; /* Light Gray 100 */ + top: 4px; + left: 4px; + width: 6px; + height: 6px; + border: none; + border-radius: 100%; + transform-origin: 8px 8px; + animation-name: spin; + animation-duration: 1000ms; + animation-timing-function: linear; + animation-iteration-count: infinite; +} + +@media (prefers-reduced-motion: reduce) { + .wpcf7-spinner::before { + animation-name: blink; + animation-duration: 2000ms; + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +@keyframes blink { + from { + opacity: 0; + } + + 50% { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.wpcf7 [inert] { + opacity: 0.5; +} + +.wpcf7 input[type="file"] { + cursor: pointer; +} + +.wpcf7 input[type="file"]:disabled { + cursor: default; +} + +.wpcf7 .wpcf7-submit:disabled { + cursor: not-allowed; +} + +.wpcf7 input[type="url"], +.wpcf7 input[type="email"], +.wpcf7 input[type="tel"] { + direction: ltr; +} + +.wpcf7-reflection > output { + display: list-item; + list-style: none; +} + +.wpcf7-reflection > output[hidden] { + display: none; +} diff --git a/wp-content/plugins/contact-form-7/includes/file.php b/wp-content/plugins/contact-form-7/includes/file.php new file mode 100644 index 0000000..2db05d1 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/file.php @@ -0,0 +1,440 @@ + false, + 'filetypes' => '', + 'limit' => MB_IN_BYTES, + ) ); + + foreach ( array( 'name', 'size', 'tmp_name', 'error' ) as $key ) { + if ( ! isset( $file[$key] ) ) { + $file[$key] = array(); + } + } + + $names = wpcf7_array_flatten( $file['name'] ); + $sizes = wpcf7_array_flatten( $file['size'] ); + $tmp_names = wpcf7_array_flatten( $file['tmp_name'] ); + $errors = wpcf7_array_flatten( $file['error'] ); + + foreach ( $errors as $error ) { + if ( ! empty( $error ) and UPLOAD_ERR_NO_FILE !== $error ) { + return new WP_Error( 'wpcf7_upload_failed_php_error', + wpcf7_get_message( 'upload_failed_php_error' ) + ); + } + } + + if ( isset( $options['schema'] ) and isset( $options['name'] ) ) { + $context = array( + 'file' => true, + 'field' => $options['name'], + ); + + foreach ( $options['schema']->validate( $context ) as $result ) { + if ( is_wp_error( $result ) ) { + return $result; + } + } + } + + // Move uploaded file to tmp dir + $uploads_dir = wpcf7_upload_tmp_dir(); + $uploads_dir = wpcf7_maybe_add_random_dir( $uploads_dir ); + + $uploaded_files = array(); + + foreach ( $names as $key => $name ) { + $tmp_name = $tmp_names[$key]; + + if ( empty( $tmp_name ) or ! is_uploaded_file( $tmp_name ) ) { + continue; + } + + $filename = $name; + $filename = wpcf7_canonicalize( $filename, array( 'strto' => 'as-is' ) ); + $filename = wpcf7_antiscript_file_name( $filename ); + + $filename = apply_filters( 'wpcf7_upload_file_name', + $filename, $name, $options + ); + + $filename = wp_unique_filename( $uploads_dir, $filename ); + $new_file = path_join( $uploads_dir, $filename ); + + // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found + if ( false === @move_uploaded_file( $tmp_name, $new_file ) ) { + return new WP_Error( 'wpcf7_upload_failed', + wpcf7_get_message( 'upload_failed' ) + ); + } + + // Make sure the uploaded file is only readable for the owner process. + $filesystem->chmod( $new_file, 0400 ); + + $uploaded_files[] = $new_file; + } + + return $uploaded_files; +} + + +add_filter( + 'wpcf7_messages', + 'wpcf7_file_messages', + 10, 1 +); + +/** + * A wpcf7_messages filter callback that adds messages for + * file-uploading fields. + */ +function wpcf7_file_messages( $messages ) { + return array_merge( $messages, array( + 'upload_failed' => array( + 'description' => __( 'Uploading a file fails for any reason', 'contact-form-7' ), + 'default' => __( 'There was an unknown error uploading the file.', 'contact-form-7' ), + ), + + 'upload_file_type_invalid' => array( + 'description' => __( 'Uploaded file is not allowed for file type', 'contact-form-7' ), + 'default' => __( 'You are not allowed to upload files of this type.', 'contact-form-7' ), + ), + + 'upload_file_too_large' => array( + 'description' => __( 'Uploaded file is too large', 'contact-form-7' ), + 'default' => __( 'The uploaded file is too large.', 'contact-form-7' ), + ), + + 'upload_failed_php_error' => array( + 'description' => __( 'Uploading a file fails for PHP error', 'contact-form-7' ), + 'default' => __( 'There was an error uploading the file.', 'contact-form-7' ), + ), + ) ); +} + + +add_filter( + 'wpcf7_form_enctype', + 'wpcf7_file_form_enctype_filter', + 10, 1 +); + +/** + * A wpcf7_form_enctype filter callback that sets the enctype attribute + * to multipart/form-data if the form has file-uploading fields. + */ +function wpcf7_file_form_enctype_filter( $enctype ) { + $multipart = (bool) wpcf7_scan_form_tags( array( + 'feature' => 'file-uploading', + ) ); + + if ( $multipart ) { + $enctype = 'multipart/form-data'; + } + + return $enctype; +} + + +/** + * Converts a MIME type string to an array of corresponding file extensions. + * + * @param string $mime MIME type. + * Wildcard (*) is available for the subtype part. + * @return array Corresponding file extensions. + */ +function wpcf7_convert_mime_to_ext( $mime ) { + static $mime_types = array(); + + $mime_types = wp_get_mime_types(); + + $results = array(); + + if ( preg_match( '%^([a-z]+)/([*]|[a-z0-9.+-]+)$%i', $mime, $matches ) ) { + foreach ( $mime_types as $key => $val ) { + if ( + '*' === $matches[2] and str_starts_with( $val, $matches[1] . '/' ) or + $val === $matches[0] + ) { + $results = array_merge( $results, explode( '|', $key ) ); + } + } + } + + $results = array_unique( $results ); + $results = array_filter( $results ); + $results = array_values( $results ); + + return $results; +} + + +/** + * Returns a formatted list of acceptable filetypes. + * + * @param string|array $types Optional. Array of filetypes. + * @param string $format Optional. Pre-defined format designator. + * @return string Formatted list of acceptable filetypes. + */ +function wpcf7_acceptable_filetypes( $types = 'default', $format = 'regex' ) { + if ( 'default' === $types or empty( $types ) ) { + $types = array( + 'audio/*', + 'video/*', + 'image/*', + ); + } else { + $types = array_map( + static function ( $type ) { + if ( is_string( $type ) ) { + return preg_split( '/[\s|,]+/', strtolower( $type ) ); + } + }, + (array) $types + ); + + $types = wpcf7_array_flatten( $types ); + $types = array_filter( array_unique( $types ) ); + } + + if ( 'attr' === $format or 'attribute' === $format ) { + $types = array_map( + static function ( $type ) { + if ( false === strpos( $type, '/' ) ) { + return sprintf( '.%s', trim( $type, '.' ) ); + } elseif ( preg_match( '%^([a-z]+)/[*]$%i', $type, $matches ) ) { + if ( + in_array( $matches[1], array( 'audio', 'video', 'image' ), true ) + ) { + return $type; + } else { + return ''; + } + } elseif ( wpcf7_convert_mime_to_ext( $type ) ) { + return $type; + } + }, + $types + ); + + $types = array_filter( $types ); + + return implode( ',', $types ); + + } elseif ( 'regex' === $format ) { + $types = array_map( + static function ( $type ) { + if ( false === strpos( $type, '/' ) ) { + return preg_quote( trim( $type, '.' ) ); + } elseif ( $type = wpcf7_convert_mime_to_ext( $type ) ) { + return $type; + } + }, + $types + ); + + $types = wpcf7_array_flatten( $types ); + $types = array_filter( array_unique( $types ) ); + + return implode( '|', $types ); + } + + return ''; +} + + +add_action( + 'wpcf7_init', + 'wpcf7_init_uploads', + 10, 0 +); + +/** + * Initializes the temporary directory for uploaded files. + */ +function wpcf7_init_uploads() { + $dir = wpcf7_upload_tmp_dir(); + + if ( ! is_dir( $dir ) or ! wp_is_writable( $dir ) ) { + return; + } + + $htaccess_file = path_join( $dir, '.htaccess' ); + + if ( file_exists( $htaccess_file ) ) { + list( $first_line_comment ) = (array) file( + $htaccess_file, + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + ); + + if ( '# Apache 2.4+' === $first_line_comment ) { + return; + } + } + + $filesystem = WPCF7_Filesystem::get_instance(); + + $htaccess_body = ' +# Apache 2.4+ + + Require all denied + + +# Apache 2.2 + + Deny from all + +'; + + $filesystem->put_contents( $htaccess_file, ltrim( $htaccess_body ) ); +} + + +/** + * Creates a child directory with a randomly generated name. + * + * @param string $dir The parent directory path. + * @return string The child directory path if created, otherwise the parent. + */ +function wpcf7_maybe_add_random_dir( $dir ) { + do { + $dir_new = path_join( $dir, zeroise( wp_rand(), 10 ) ); + } while ( file_exists( $dir_new ) ); + + if ( wp_mkdir_p( $dir_new ) ) { + return $dir_new; + } + + return $dir; +} + + +/** + * Returns the directory path for uploaded files. + * + * @return string Directory path. + */ +function wpcf7_upload_tmp_dir() { + static $output = ''; + + if ( $output ) { + return $output; + } + + if ( defined( 'WPCF7_UPLOADS_TMP_DIR' ) ) { + $dir = path_join( WP_CONTENT_DIR, WPCF7_UPLOADS_TMP_DIR ); + wp_mkdir_p( $dir ); + + if ( wpcf7_is_file_path_in_content_dir( $dir ) ) { + return $output = $dir; + } + } + + $dir = path_join( wpcf7_upload_dir( 'dir' ), 'wpcf7_uploads' ); + wp_mkdir_p( $dir ); + + return $output = $dir; +} + + +add_action( + 'shutdown', + 'wpcf7_cleanup_upload_files', + 20, 0 +); + +/** + * Cleans up files in the temporary directory for uploaded files. + * + * @param int $seconds Files older than this are removed. Default 60. + * @param int $max Maximum number of files to be removed in a function call. + * Default 100. + */ +function wpcf7_cleanup_upload_files( $seconds = 60, $max = 100 ) { + $dir = trailingslashit( wpcf7_upload_tmp_dir() ); + + if ( + ! is_dir( $dir ) or + ! is_readable( $dir ) or + ! wp_is_writable( $dir ) + ) { + return; + } + + $seconds = absint( $seconds ); + $max = absint( $max ); + $count = 0; + + if ( $handle = opendir( $dir ) ) { + while ( false !== ( $file = readdir( $handle ) ) ) { + if ( '.' === $file or '..' === $file or '.htaccess' === $file ) { + continue; + } + + $mtime = @filemtime( path_join( $dir, $file ) ); + + if ( $mtime and time() < $mtime + $seconds ) { // less than $seconds old + continue; + } + + wpcf7_rmdir_p( path_join( $dir, $file ) ); + $count += 1; + + if ( $max <= $count ) { + break; + } + } + + closedir( $handle ); + } +} + + +add_action( + 'wpcf7_admin_warnings', + 'wpcf7_file_display_warning_message', + 10, 3 +); + +/** + * Displays warning messages about file-uploading fields. + */ +function wpcf7_file_display_warning_message( $page, $action, $object ) { + if ( $object instanceof WPCF7_ContactForm ) { + $contact_form = $object; + } else { + return; + } + + $has_tags = (bool) $contact_form->scan_form_tags( array( + 'feature' => 'file-uploading', + ) ); + + if ( ! $has_tags ) { + return; + } + + $uploads_dir = wpcf7_upload_tmp_dir(); + + if ( ! is_dir( $uploads_dir ) or ! wp_is_writable( $uploads_dir ) ) { + wp_admin_notice( + sprintf( + /* translators: %s: the path of the temporary folder */ + __( 'This contact form has file uploading fields, but the temporary folder for the files (%s) does not exist or is not writable. You can create the folder or change its permission manually.', 'contact-form-7' ), + $uploads_dir + ), + array( 'type' => 'warning' ) + ); + } +} diff --git a/wp-content/plugins/contact-form-7/includes/filesystem.php b/wp-content/plugins/contact-form-7/includes/filesystem.php new file mode 100644 index 0000000..6f98101 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/filesystem.php @@ -0,0 +1,127 @@ +connect(); + } + + + /** + * Connects to the filesystem. + * + * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. + */ + private function connect() { + global $wp_filesystem; + + if ( $this->filesystem ) { + return false; + } + + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; + + ob_start(); + $credentials = request_filesystem_credentials( '' ); + ob_end_clean(); + + if ( false === $credentials or ! WP_Filesystem( $credentials ) ) { + wp_trigger_error( + __FUNCTION__, + __( 'Could not access filesystem.', 'contact-form-7' ) + ); + } + + if ( $wp_filesystem instanceof WP_Filesystem_Base ) { + $this->filesystem = $wp_filesystem; + } else { + $this->filesystem = new WP_Filesystem_Direct( 1 ); + } + + if ( ! defined( 'FS_CHMOD_DIR' ) ) { + define( 'FS_CHMOD_DIR', fileperms( ABSPATH ) & 0777 | 0755 ); + } + + if ( ! defined( 'FS_CHMOD_FILE' ) ) { + define( 'FS_CHMOD_FILE', fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ); + } + } + + + /** + * Changes filesystem permissions. + * + * @param string $file Path to the file. + * @param int|false $mode Optional. The permissions as octal number. + * @param bool $recursive Optional. If set to true, + * changes file permissions recursively. Default false. + * @return bool True on success, false on failure. + */ + public function chmod( $file, $mode = false, $recursive = false ) { + return $this->filesystem->chmod( $file, $mode, $recursive ); + } + + + /** + * Deletes a file or directory. + * + * @param string $file Path to the file or directory. + * @param bool $recursive Optional. If set to true, deletes + * files and folders recursively. Default false. + * @param string|false $type Type of resource. + * 'f' for file, 'd' for directory. Default false. + * @return bool True on success, false on failure. + */ + public function delete( $file, $recursive = false, $type = false ) { + return $this->filesystem->delete( $file, $recursive, $type ); + } + + + /** + * Writes a string to a file. + * + * @param string $file Path to the file where to write the data. + * @param string $contents The data to write. + * @param int $mode The file permissions as octal number. + * @return bool True on success, false on failure. + */ + public function put_contents( $file, $contents, $mode = false ) { + return $this->filesystem->put_contents( $file, $contents, $mode ); + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/form-tag.php b/wp-content/plugins/contact-form-7/includes/form-tag.php new file mode 100644 index 0000000..c816d35 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/form-tag.php @@ -0,0 +1,618 @@ + $value ) { + if ( property_exists( __CLASS__, $key ) ) { + $this->{$key} = $value; + } + } + } + } + + + /** + * Returns true if the type has a trailing asterisk. + */ + public function is_required() { + return str_ends_with( $this->type, '*' ); + } + + + /** + * Returns true if the form-tag has a specified option. + */ + public function has_option( $option_name ) { + $pattern = sprintf( '/^%s(:.+)?$/i', preg_quote( $option_name, '/' ) ); + return (bool) preg_grep( $pattern, $this->options ); + } + + + /** + * Retrieves option values with the specified option name. + * + * @param string $option_name Option name. + * @param string $pattern Optional. A regular expression pattern or one of + * the keys of preset patterns. If specified, only options + * whose value part matches this pattern will be returned. + * @param bool $single Optional. If true, only the first matching option + * will be returned. Default false. + * @return string|array|bool The option value or an array of option values. + * False if there is no option matches the pattern. + */ + public function get_option( $option_name, $pattern = '', $single = false ) { + $preset_patterns = array( + 'date' => '[0-9]{4}-[0-9]{2}-[0-9]{2}', + 'int' => '[0-9]+', + 'signed_int' => '[-]?[0-9]+', + 'num' => '(?:[0-9]+|(?:[0-9]+)?[.][0-9]+)', + 'signed_num' => '[-]?(?:[0-9]+|(?:[0-9]+)?[.][0-9]+)', + 'class' => '[-0-9a-zA-Z_]+', + 'id' => '[-0-9a-zA-Z_]+', + ); + + if ( isset( $preset_patterns[$pattern] ) ) { + $pattern = $preset_patterns[$pattern]; + } + + if ( '' === $pattern ) { + $pattern = '.+'; + } + + $pattern = sprintf( + '/^%s:%s$/i', + preg_quote( $option_name, '/' ), + $pattern + ); + + if ( $single ) { + $matches = $this->get_first_match_option( $pattern ); + + if ( ! $matches ) { + return false; + } + + return substr( $matches[0], strlen( $option_name ) + 1 ); + } else { + $matches_a = $this->get_all_match_options( $pattern ); + + if ( ! $matches_a ) { + return false; + } + + $results = array(); + + foreach ( $matches_a as $matches ) { + $results[] = substr( $matches[0], strlen( $option_name ) + 1 ); + } + + return $results; + } + } + + + /** + * Retrieves the id option value from the form-tag. + */ + public function get_id_option() { + static $used = array(); + + $option = $this->get_option( 'id', 'id', true ); + + if ( + ! $option or + str_starts_with( $option, 'wpcf7' ) or + in_array( $option, $used, true ) + ) { + return false; + } + + $used[] = $option; + + return $option; + } + + + /** + * Retrieves the class option value from the form-tag. + * + * @param string|array $default_classes Optional. Preset classes as an array + * or a whitespace-separated list. Default empty string. + * @return string|bool A whitespace-separated list of classes. + * False if there is no class to return. + */ + public function get_class_option( $default_classes = '' ) { + if ( is_string( $default_classes ) ) { + $default_classes = explode( ' ', $default_classes ); + } + + $options = array_merge( + (array) $default_classes, + (array) $this->get_option( 'class' ) + ); + + $options = array_map( 'sanitize_html_class', $options ); + $options = array_filter( array_unique( $options ) ); + + if ( empty( $options ) ) { + return false; + } + + return implode( ' ', $options ); + } + + + /** + * Retrieves the autocomplete option value from the form-tag. + * + * @return string|bool A whitespace-separated list of tokens. + * False if there is no token to return. + */ + public function get_autocomplete_option() { + $options = (array) $this->get_option( 'autocomplete', '[-0-9a-zA-Z|]+' ); + + $options = array_reduce( $options, static function ( $carry, $item ) { + return array_merge( $carry, + array_map( 'strtolower', explode( '|', $item ) ) + ); + }, array() ); + + $options = array_filter( $options, static function ( $item ) { + return preg_match( '/^[a-z]+(?:-[0-9a-z]+)*$/', $item ); + } ); + + $options = array_unique( $options ); + + if ( empty( $options ) ) { + return false; + } elseif ( in_array( 'off', $options, true ) ) { + return 'off'; + } elseif ( in_array( 'on', $options, true ) ) { + return 'on'; + } else { + return implode( ' ', $options ); + } + } + + + /** + * Retrieves the size option value from the form-tag. + * + * @param string $default_value Optional default value. + * @return string The option value. + */ + public function get_size_option( $default_value = false ) { + $option = $this->get_option( 'size', 'int', true ); + + if ( $option ) { + return $option; + } + + $matches_a = $this->get_all_match_options( '%^([0-9]*)/[0-9]*$%' ); + + foreach ( $matches_a as $matches ) { + if ( isset( $matches[1] ) and '' !== $matches[1] ) { + return $matches[1]; + } + } + + return $default_value; + } + + + /** + * Retrieves the maxlength option value from the form-tag. + * + * @param string $default_value Optional default value. + * @return string The option value. + */ + public function get_maxlength_option( $default_value = false ) { + $option = $this->get_option( 'maxlength', 'int', true ); + + if ( $option ) { + return $option; + } + + $matches_a = $this->get_all_match_options( + '%^(?:[0-9]*x?[0-9]*)?/([0-9]+)$%' + ); + + foreach ( $matches_a as $matches ) { + if ( isset( $matches[1] ) and '' !== $matches[1] ) { + return $matches[1]; + } + } + + return $default_value; + } + + + /** + * Retrieves the minlength option value from the form-tag. + * + * @param string $default_value Optional default value. + * @return string The option value. + */ + public function get_minlength_option( $default_value = false ) { + $option = $this->get_option( 'minlength', 'int', true ); + + if ( $option ) { + return $option; + } else { + return $default_value; + } + } + + + /** + * Retrieves the cols option value from the form-tag. + * + * @param string $default_value Optional default value. + * @return string The option value. + */ + public function get_cols_option( $default_value = false ) { + $option = $this->get_option( 'cols', 'int', true ); + + if ( $option ) { + return $option; + } + + $matches_a = $this->get_all_match_options( + '%^([0-9]*)x([0-9]*)(?:/[0-9]+)?$%' + ); + + foreach ( $matches_a as $matches ) { + if ( isset( $matches[1] ) and '' !== $matches[1] ) { + return $matches[1]; + } + } + + return $default_value; + } + + + /** + * Retrieves the rows option value from the form-tag. + * + * @param string $default_value Optional default value. + * @return string The option value. + */ + public function get_rows_option( $default_value = false ) { + $option = $this->get_option( 'rows', 'int', true ); + + if ( $option ) { + return $option; + } + + $matches_a = $this->get_all_match_options( + '%^([0-9]*)x([0-9]*)(?:/[0-9]+)?$%' + ); + + foreach ( $matches_a as $matches ) { + if ( isset( $matches[2] ) and '' !== $matches[2] ) { + return $matches[2]; + } + } + + return $default_value; + } + + + /** + * Retrieves a date-type option value from the form-tag. + * + * @param string $option_name A date-type option name, such as 'min' or 'max'. + * @return string|bool The option value in YYYY-MM-DD format. False if the + * option does not exist or the date value is invalid. + */ + public function get_date_option( $option_name ) { + $option_value = $this->get_option( $option_name, '', true ); + + if ( empty( $option_value ) ) { + return false; + } + + $date = apply_filters( 'wpcf7_form_tag_date_option', + null, + array( $option_name => $option_value ) + ); + + if ( $date ) { + $date_pattern = '/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/'; + + if ( + preg_match( $date_pattern, $date, $matches ) and + checkdate( $matches[2], $matches[3], $matches[1] ) + ) { + return $date; + } + } else { + $datetime_obj = date_create_immutable( + preg_replace( '/[_]+/', ' ', $option_value ), + wp_timezone() + ); + + if ( $datetime_obj ) { + return $datetime_obj->format( 'Y-m-d' ); + } + } + + return false; + } + + + /** + * Retrieves the default option value from the form-tag. + * + * @param string|array $default_value Optional default value. + * @param string|array $args Optional options for the option value retrieval. + * @return string|array The option value. If the multiple option is enabled, + * an array of option values. + */ + public function get_default_option( $default_value = '', $args = '' ) { + $args = wp_parse_args( $args, array( + 'multiple' => false, + 'shifted' => false, + ) ); + + $options = (array) $this->get_option( 'default' ); + $values = array(); + + if ( empty( $options ) ) { + return $args['multiple'] ? $values : $default_value; + } + + foreach ( $options as $opt ) { + $opt = sanitize_key( $opt ); + + if ( 'user_' === substr( $opt, 0, 5 ) and is_user_logged_in() ) { + $primary_props = array( 'user_login', 'user_email', 'user_url' ); + $opt = in_array( $opt, $primary_props, true ) ? $opt : substr( $opt, 5 ); + + $user = wp_get_current_user(); + $user_prop = $user->get( $opt ); + + if ( ! empty( $user_prop ) ) { + if ( $args['multiple'] ) { + $values[] = $user_prop; + } else { + return $user_prop; + } + } + + } elseif ( 'post_meta' === $opt and in_the_loop() ) { + if ( $args['multiple'] ) { + $values = array_merge( $values, + get_post_meta( get_the_ID(), $this->name ) + ); + } else { + $val = (string) get_post_meta( get_the_ID(), $this->name, true ); + + if ( strlen( $val ) ) { + return $val; + } + } + + } elseif ( + 'get' === $opt and + $vals = wpcf7_superglobal_get( $this->name ) + ) { + $vals = array_map( 'wpcf7_sanitize_query_var', (array) $vals ); + + if ( $args['multiple'] ) { + $values = array_merge( $values, $vals ); + } else { + $val = isset( $vals[0] ) ? (string) $vals[0] : ''; + + if ( strlen( $val ) ) { + return $val; + } + } + + } elseif ( + 'post' === $opt and + $vals = wpcf7_superglobal_post( $this->name ) + ) { + $vals = array_map( 'wpcf7_sanitize_query_var', (array) $vals ); + + if ( $args['multiple'] ) { + $values = array_merge( $values, $vals ); + } else { + $val = isset( $vals[0] ) ? (string) $vals[0] : ''; + + if ( strlen( $val ) ) { + return $val; + } + } + + } elseif ( 'shortcode_attr' === $opt ) { + if ( $contact_form = WPCF7_ContactForm::get_current() ) { + $val = $contact_form->shortcode_attr( $this->name ); + + if ( isset( $val ) and strlen( $val ) ) { + if ( $args['multiple'] ) { + $values[] = $val; + } else { + return $val; + } + } + } + + } elseif ( preg_match( '/^[0-9_]+$/', $opt ) ) { + $nums = explode( '_', $opt ); + + foreach ( $nums as $num ) { + $num = absint( $num ); + $num = $args['shifted'] ? $num : $num - 1; + + if ( isset( $this->values[$num] ) ) { + if ( $args['multiple'] ) { + $values[] = $this->values[$num]; + } else { + return $this->values[$num]; + } + } + } + } + } + + if ( $args['multiple'] ) { + $values = array_unique( $values ); + return $values; + } else { + return $default_value; + } + } + + + /** + * Retrieves the data option value from the form-tag. + * + * @param string|array $args Optional options for the option value retrieval. + * @return mixed The option value. + */ + public function get_data_option( $args = '' ) { + $options = (array) $this->get_option( 'data' ); + + return apply_filters( 'wpcf7_form_tag_data_option', null, $options, $args ); + } + + + /** + * Retrieves the limit option value from the form-tag. + * + * @param int $default_value Optional default value. Default 1048576. + * @return int The option value. + */ + public function get_limit_option( $default_value = MB_IN_BYTES ) { + $pattern = '/^limit:([1-9][0-9]*)([kKmM]?[bB])?$/'; + + $matches = $this->get_first_match_option( $pattern ); + + if ( $matches ) { + $size = (int) $matches[1]; + + if ( ! empty( $matches[2] ) ) { + $kbmb = strtolower( $matches[2] ); + + if ( 'kb' === $kbmb ) { + $size *= KB_IN_BYTES; + } elseif ( 'mb' === $kbmb ) { + $size *= MB_IN_BYTES; + } + } + + return $size; + } + + return (int) $default_value; + } + + + /** + * Retrieves the value of the first option matches the given + * regular expression pattern. + * + * @param string $pattern Regular expression pattern. + * @return array|bool Option value as an array of matched strings. + * False if there is no option matches the pattern. + */ + public function get_first_match_option( $pattern ) { + foreach ( (array) $this->options as $option ) { + if ( preg_match( $pattern, $option, $matches ) ) { + return $matches; + } + } + + return false; + } + + + /** + * Retrieves values of options that match the given + * regular expression pattern. + * + * @param string $pattern Regular expression pattern. + * @return array Array of arrays of strings that match the pattern. + */ + public function get_all_match_options( $pattern ) { + $result = array(); + + foreach ( (array) $this->options as $option ) { + if ( preg_match( $pattern, $option, $matches ) ) { + $result[] = $matches; + } + } + + return $result; + } + + + /** + * Assigns a value to the specified offset. + * + * @link https://www.php.net/manual/en/arrayaccess.offsetset.php + */ + #[ReturnTypeWillChange] + public function offsetSet( $offset, $value ) { + if ( property_exists( __CLASS__, $offset ) ) { + $this->{$offset} = $value; + } + } + + + /** + * Returns the value at specified offset. + * + * @link https://www.php.net/manual/en/arrayaccess.offsetget.php + */ + #[ReturnTypeWillChange] + public function offsetGet( $offset ) { + if ( property_exists( __CLASS__, $offset ) ) { + return $this->{$offset}; + } + + return null; + } + + + /** + * Returns true if the specified offset exists. + * + * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php + */ + #[ReturnTypeWillChange] + public function offsetExists( $offset ) { + return property_exists( __CLASS__, $offset ); + } + + + /** + * Unsets an offset. + * + * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php + */ + #[ReturnTypeWillChange] + public function offsetUnset( $offset ) { + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/form-tags-manager.php b/wp-content/plugins/contact-form-7/includes/form-tags-manager.php new file mode 100644 index 0000000..6088381 --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/form-tags-manager.php @@ -0,0 +1,615 @@ +add( $tag_types, $callback, $features ); +} + + +/** + * Wrapper function of WPCF7_FormTagsManager::remove(). + */ +function wpcf7_remove_form_tag( $tag_type ) { + $manager = WPCF7_FormTagsManager::get_instance(); + + return $manager->remove( $tag_type ); +} + + +/** + * Wrapper function of WPCF7_FormTagsManager::replace_all(). + */ +function wpcf7_replace_all_form_tags( $content ) { + $manager = WPCF7_FormTagsManager::get_instance(); + + return $manager->replace_all( $content ); +} + + +/** + * Wrapper function of WPCF7_ContactForm::scan_form_tags(). + */ +function wpcf7_scan_form_tags( $cond = null ) { + $contact_form = WPCF7_ContactForm::get_current(); + + if ( $contact_form ) { + return $contact_form->scan_form_tags( $cond ); + } + + return array(); +} + + +/** + * Wrapper function of WPCF7_FormTagsManager::tag_type_supports(). + */ +function wpcf7_form_tag_supports( $tag_type, $feature ) { + $manager = WPCF7_FormTagsManager::get_instance(); + + return $manager->tag_type_supports( $tag_type, $feature ); +} + + +/** + * The singleton instance of this class manages the collection of form-tags. + */ +class WPCF7_FormTagsManager { + + private static $instance; + + private $tag_types = array(); + private $scanned_tags = null; // Tags scanned at the last time of scan() + private $placeholders = array(); + + private function __construct() {} + + + /** + * Returns the singleton instance. + * + * @return WPCF7_FormTagsManager The singleton manager. + */ + public static function get_instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + + return self::$instance; + } + + + /** + * Returns scanned form-tags. + * + * @return array Array of WPCF7_FormTag objects. + */ + public function get_scanned_tags() { + return $this->scanned_tags; + } + + + /** + * Registers form-tag types to the manager. + * + * @param string|array $tag_types The name of the form-tag type or + * an array of the names. + * @param callable $callback The callback to generates a form control HTML + * for a form-tag in this type. + * @param string|array $features Optional. Features a form-tag + * in this type supports. + */ + public function add( $tag_types, $callback, $features = '' ) { + if ( ! is_callable( $callback ) ) { + return; + } + + if ( true === $features ) { // for back-compat + $features = array( 'name-attr' => true ); + } + + $features = wp_parse_args( $features, array() ); + + $tag_types = array_filter( array_unique( (array) $tag_types ) ); + + foreach ( $tag_types as $tag_type ) { + $tag_type = $this->sanitize_tag_type( $tag_type ); + + if ( ! $this->tag_type_exists( $tag_type ) ) { + $this->tag_types[$tag_type] = array( + 'function' => $callback, + 'features' => $features, + ); + } + } + } + + + /** + * Returns true if the given tag type exists. + */ + public function tag_type_exists( $tag_type ) { + return isset( $this->tag_types[$tag_type] ); + } + + + /** + * Returns true if the tag type supports the features. + * + * @param string $tag_type The name of the form-tag type. + * @param array|string $features The feature to check or an array of features. + * @return bool True if the form-tag type supports at least one of + * the given features, false otherwise. + */ + public function tag_type_supports( $tag_type, $features ) { + $features = array_filter( (array) $features ); + + if ( isset( $this->tag_types[$tag_type]['features'] ) ) { + return (bool) array_intersect( + array_keys( array_filter( $this->tag_types[$tag_type]['features'] ) ), + $features + ); + } + + return false; + } + + + /** + * Returns form-tag types that support the given features. + * + * @param array|string $features Optional. The feature to check or + * an array of features. Default empty array. + * @param bool $invert Optional. If this value is true, returns form-tag + * types that do not support the given features. Default false. + * @return array An array of form-tag types. If the $features param is empty, + * returns all form-tag types that have been registered. + */ + public function collect_tag_types( $features = array(), $invert = false ) { + $tag_types = array_keys( $this->tag_types ); + + if ( empty( $features ) ) { + return $tag_types; + } + + $output = array(); + + foreach ( $tag_types as $tag_type ) { + if ( + ! $invert and $this->tag_type_supports( $tag_type, $features ) or + $invert and ! $this->tag_type_supports( $tag_type, $features ) + ) { + $output[] = $tag_type; + } + } + + return $output; + } + + + /** + * Sanitizes the form-tag type name. + */ + private function sanitize_tag_type( $tag_type ) { + $tag_type = preg_replace( '/[^a-zA-Z0-9_*]+/', '_', $tag_type ); + $tag_type = rtrim( $tag_type, '_' ); + $tag_type = strtolower( $tag_type ); + return $tag_type; + } + + + /** + * Deregisters the form-tag type. + */ + public function remove( $tag_type ) { + unset( $this->tag_types[$tag_type] ); + } + + + /** + * Normalizes the text content that includes form-tags. + */ + public function normalize( $content ) { + if ( empty( $this->tag_types ) ) { + return $content; + } + + $content = preg_replace_callback( + '/' . $this->tag_regex() . '/su', + array( $this, 'normalize_callback' ), + $content + ); + + return $content; + } + + + /** + * The callback function used within normalize(). + */ + private function normalize_callback( $matches ) { + // allow [[foo]] syntax for escaping a tag + if ( '[' === $matches[1] and ']' === $matches[6] ) { + return $matches[0]; + } + + $tag = $matches[2]; + + $attr = trim( preg_replace( '/[\r\n\t ]+/', ' ', $matches[3] ) ); + $attr = strtr( $attr, array( '<' => '<', '>' => '>' ) ); + + $content = trim( $matches[5] ); + $content = str_replace( "\n", '', $content ); + + $result = $matches[1] . '[' . $tag + . ( $attr ? ' ' . $attr : '' ) + . ( $matches[4] ? ' ' . $matches[4] : '' ) + . ']' + . ( $content ? $content . '[/' . $tag . ']' : '' ) + . $matches[6]; + + return $result; + } + + + /** + * Replace all form-tags in the given text with placeholders. + */ + public function replace_with_placeholders( $content ) { + if ( empty( $this->tag_types ) ) { + return $content; + } + + $this->placeholders = array(); + + $callback = function ( $matches ) { + // Allow [[foo]] syntax for escaping a tag. + if ( '[' === $matches[1] and ']' === $matches[6] ) { + return $matches[0]; + } + + $tag = $matches[0]; + $tag_type = $matches[2]; + + $block_or_hidden = $this->tag_type_supports( + $tag_type, + array( 'display-block', 'display-hidden' ) + ); + + if ( $block_or_hidden ) { + $placeholder_tag_name = WPCF7_HTMLFormatter::placeholder_block; + } else { + $placeholder_tag_name = WPCF7_HTMLFormatter::placeholder_inline; + } + + $placeholder = sprintf( + '<%1$s id="%2$s" />', + $placeholder_tag_name, + hash( 'sha256', $tag ) + ); + + list( $placeholder ) = + WPCF7_HTMLFormatter::normalize_start_tag( $placeholder ); + + $this->placeholders[$placeholder] = $tag; + + return $placeholder; + }; + + return preg_replace_callback( + '/' . $this->tag_regex() . '/su', + $callback, + $content + ); + } + + + /** + * Replace placeholders in the given text with original form-tags. + */ + public function restore_from_placeholders( $content ) { + return str_replace( + array_keys( $this->placeholders ), + array_values( $this->placeholders ), + $content + ); + } + + + /** + * Replaces all form-tags in the text content. + * + * @param string $content The text content including form-tags. + * @return string The result of replacements. + */ + public function replace_all( $content ) { + return $this->scan( $content, true ); + } + + + /** + * Scans form-tags in the text content. + * + * @param string $content The text content including form-tags. + * @param bool $replace Optional. Whether scanned form-tags will be + * replaced. Default false. + * @return array|string An array of scanned form-tags if $replace is false. + * Otherwise text that scanned form-tags are replaced. + */ + public function scan( $content, $replace = false ) { + $this->scanned_tags = array(); + + if ( empty( $this->tag_types ) ) { + if ( $replace ) { + return $content; + } else { + return $this->scanned_tags; + } + } + + if ( $replace ) { + $content = preg_replace_callback( + '/' . $this->tag_regex() . '/su', + array( $this, 'replace_callback' ), + $content + ); + + return $content; + } else { + preg_replace_callback( + '/' . $this->tag_regex() . '/su', + array( $this, 'scan_callback' ), + $content + ); + + return $this->scanned_tags; + } + } + + + /** + * Filters form-tags based on a condition array argument. + * + * @param array|string $input The original form-tags collection. + * If it is a string, scans form-tags from it. + * @param array $cond The conditions that filtering will be based on. + * @return array The filtered form-tags collection. + */ + public function filter( $input, $cond ) { + if ( is_array( $input ) ) { + $tags = $input; + } elseif ( is_string( $input ) ) { + $tags = $this->scan( $input ); + } else { + $tags = $this->scanned_tags; + } + + $cond = wp_parse_args( $cond, array( + 'type' => array(), + 'basetype' => array(), + 'name' => array(), + 'feature' => array(), + ) ); + + $cond = array_map( static function ( $c ) { + return array_filter( array_map( 'trim', (array) $c ) ); + }, $cond ); + + $tags = array_filter( + (array) $tags, + function ( $tag ) use ( $cond ) { + $tag = new WPCF7_FormTag( $tag ); + + if ( + $cond['type'] and + ! in_array( $tag->type, $cond['type'], true ) + ) { + return false; + } + + if ( + $cond['basetype'] and + ! in_array( $tag->basetype, $cond['basetype'], true ) + ) { + return false; + } + + if ( + $cond['name'] and + ! in_array( $tag->name, $cond['name'], true ) + ) { + return false; + } + + foreach ( $cond['feature'] as $feature ) { + if ( str_starts_with( $feature, '!' ) ) { // Negation + $feature = trim( substr( $feature, 1 ) ); + + if ( $this->tag_type_supports( $tag->type, $feature ) ) { + return false; + } + } else { + if ( ! $this->tag_type_supports( $tag->type, $feature ) ) { + return false; + } + } + } + + return true; + } + ); + + return array_values( $tags ); + } + + + /** + * Returns the regular expression for a form-tag. + */ + private function tag_regex() { + $tag_types = implode( '|', + array_map( 'preg_quote', array_keys( $this->tag_types ) ) + ); + + $whitespaces = wpcf7_get_unicode_whitespaces(); + + return '(\[?)' + . '\[(' . $tag_types . ')' + . '(?:[' . $whitespaces . ']+(.*?))?' + . '(?:[' . $whitespaces . ']+(\/))?\]' + . '(?:([^[]*?)\[\/\2\])?' + . '(\]?)'; + } + + + /** + * The callback function for the form-tag replacement. + */ + private function replace_callback( $matches ) { + return $this->scan_callback( $matches, true ); + } + + + /** + * The callback function for the form-tag scanning. + */ + private function scan_callback( $matches, $replace = false ) { + // allow [[foo]] syntax for escaping a tag + if ( '[' === $matches[1] and ']' === $matches[6] ) { + return substr( $matches[0], 1, -1 ); + } + + $tag_type = $matches[2]; + $tag_basetype = trim( $tag_type, '*' ); + $attr = $this->parse_atts( $matches[3] ); + + $scanned_tag = array( + 'type' => $tag_type, + 'basetype' => $tag_basetype, + 'raw_name' => '', + 'name' => '', + 'options' => array(), + 'raw_values' => array(), + 'values' => array(), + 'pipes' => null, + 'labels' => array(), + 'attr' => '', + 'content' => '', + ); + + if ( $this->tag_type_supports( $tag_type, 'singular' ) ) { + $tags_in_same_basetype = $this->filter( + $this->scanned_tags, + array( 'basetype' => $tag_basetype ) + ); + + if ( $tags_in_same_basetype ) { + // Another tag in the same base type already exists. Ignore this one. + return $matches[0]; + } + } + + if ( $this->tag_type_supports( $tag_type, 'name-attr' ) ) { + if ( ! is_array( $attr ) ) { + return $matches[0]; // Invalid form-tag. + } + + $scanned_tag['raw_name'] = (string) array_shift( $attr['options'] ); + + if ( ! wpcf7_is_name( $scanned_tag['raw_name'] ) ) { + return $matches[0]; // Invalid name is used. Ignore this tag. + } + + $scanned_tag['name'] = strtr( $scanned_tag['raw_name'], '.', '_' ); + } + + if ( is_array( $attr ) ) { + $scanned_tag['options'] = (array) $attr['options']; + $scanned_tag['raw_values'] = (array) $attr['values']; + + if ( WPCF7_USE_PIPE ) { + $pipes = new WPCF7_Pipes( $scanned_tag['raw_values'] ); + $scanned_tag['values'] = $pipes->collect_befores(); + $scanned_tag['pipes'] = $pipes; + } else { + $scanned_tag['values'] = $scanned_tag['raw_values']; + } + + $scanned_tag['labels'] = $scanned_tag['values']; + + } else { + $scanned_tag['attr'] = $attr; + } + + $scanned_tag['values'] = array_map( 'trim', $scanned_tag['values'] ); + $scanned_tag['labels'] = array_map( 'trim', $scanned_tag['labels'] ); + + $content = trim( $matches[5] ); + $content = preg_replace( "/$/m", '', $content ); + $scanned_tag['content'] = $content; + + $scanned_tag = apply_filters( 'wpcf7_form_tag', $scanned_tag, $replace ); + + $scanned_tag = new WPCF7_FormTag( $scanned_tag ); + + $this->scanned_tags[] = $scanned_tag; + + if ( $replace ) { + $callback = $this->tag_types[$tag_type]['function']; + return $matches[1] . call_user_func( $callback, $scanned_tag ) . $matches[6]; + } else { + return $matches[0]; + } + } + + + /** + * Parses the attributes of a form-tag to extract the name, + * options, and values. + * + * @param string $text Attributes of a form-tag. + * @return array|string An associative array of the options and values + * if the input is in the correct syntax, + * otherwise the input text itself. + */ + private function parse_atts( $text ) { + $atts = array( + 'options' => array(), + 'values' => array(), + ); + + $whitespaces = wpcf7_get_unicode_whitespaces(); + + $text = preg_replace( '/[\x{00a0}\x{200b}]+/u', ' ', $text ); + $text = wpcf7_strip_whitespaces( $text ); + + $pattern = '%^([-+*=0-9a-zA-Z:.!?#$&@_/|\%' . $whitespaces . ']*?)' + . '((?:' + . '[' . $whitespaces . ']*"[^"]*"' + . '|' + . '[' . $whitespaces . ']*\'[^\']*\'' + . ')*)$%u'; + + if ( preg_match( $pattern, $text, $matches ) ) { + if ( ! empty( $matches[1] ) ) { + $atts['options'] = preg_split( + sprintf( '/[%s]+/u', $whitespaces ), + wpcf7_strip_whitespaces( $matches[1] ) + ); + } + + if ( ! empty( $matches[2] ) ) { + preg_match_all( '/"[^"]*"|\'[^\']*\'/', $matches[2], $matched_values ); + $atts['values'] = wpcf7_strip_quote_deep( $matched_values[0] ); + } + } else { + $atts = $text; + } + + return $atts; + } + +} diff --git a/wp-content/plugins/contact-form-7/includes/formatting.php b/wp-content/plugins/contact-form-7/includes/formatting.php new file mode 100644 index 0000000..37210db --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/formatting.php @@ -0,0 +1,653 @@ +/is', + static function ( $matches ) use ( &$placeholders ) { + $placeholder = sprintf( + '<%1$s id="%2$s" />', + WPCF7_HTMLFormatter::placeholder_inline, + hash( 'sha256', $matches[0] ) + ); + + list( $placeholder ) = + WPCF7_HTMLFormatter::normalize_start_tag( $placeholder ); + + $placeholders[$placeholder] = $matches[0]; + + return $placeholder; + }, + $input + ); + + $formatter = new WPCF7_HTMLFormatter( array( + 'auto_br' => $br, + 'allowed_html' => null, + ) ); + + $chunks = $formatter->separate_into_chunks( $input ); + + $output = $formatter->format( $chunks ); + + // Restore from placeholders. + $output = str_replace( + array_keys( $placeholders ), + array_values( $placeholders ), + $output + ); + + return $output; +} + + +/** + * Newline preservation help function for wpcf7_autop(). + * + * @deprecated 5.7 Unnecessary to use any more. + * + * @param array $matches preg_replace_callback() matches array. + * @return string Text including newline placeholders. + */ +function wpcf7_autop_preserve_newline_callback( $matches ) { + return str_replace( "\n", '', $matches[0] ); +} + + +/** + * Sanitizes the query variables. + * + * @param string $text Query variable. + * @return string Text sanitized. + */ +function wpcf7_sanitize_query_var( $text ) { + $text = wp_unslash( $text ); + $text = wp_check_invalid_utf8( $text ); + + if ( false !== strpos( $text, '<' ) ) { + $text = wp_pre_kses_less_than( $text ); + $text = wp_strip_all_tags( $text ); + } + + $text = preg_replace( '/%[a-f0-9]{2}/i', '', $text ); + $text = preg_replace( '/ +/', ' ', $text ); + $text = trim( $text, ' ' ); + + return $text; +} + + +/** + * Strips quote characters surrounding the input. + * + * @param string $text Input text. + * @return string Processed output. + */ +function wpcf7_strip_quote( $text ) { + $text = wpcf7_strip_whitespaces( $text ); + + if ( preg_match( '/^"(.*)"$/s', $text, $matches ) ) { + $text = $matches[1]; + } elseif ( preg_match( "/^'(.*)'$/s", $text, $matches ) ) { + $text = $matches[1]; + } + + return $text; +} + + +/** + * Navigates through an array, object, or scalar, and + * strips quote characters surrounding the each value. + * + * @param mixed $input The array or string to be processed. + * @return mixed Processed value. + */ +function wpcf7_strip_quote_deep( $input ) { + if ( is_string( $input ) ) { + return wpcf7_strip_quote( $input ); + } + + if ( is_array( $input ) ) { + $result = array(); + + foreach ( $input as $key => $text ) { + $result[$key] = wpcf7_strip_quote_deep( $text ); + } + + return $result; + } +} + + +/** + * Normalizes newline characters. + * + * @param string $text Input text. + * @param string $to Optional. The newline character that is used in the output. + * @return string Normalized text. + */ +function wpcf7_normalize_newline( $text, $to = "\n" ) { + if ( ! is_string( $text ) ) { + return $text; + } + + $nls = array( "\r\n", "\r", "\n" ); + + if ( ! in_array( $to, $nls, true ) ) { + return $text; + } + + return str_replace( $nls, $to, $text ); +} + + +/** + * Navigates through an array, object, or scalar, and + * normalizes newline characters in the each value. + * + * @param mixed $input The array or string to be processed. + * @param string $to Optional. The newline character that is used in the output. + * @return mixed Processed value. + */ +function wpcf7_normalize_newline_deep( $input, $to = "\n" ) { + if ( is_array( $input ) ) { + $result = array(); + + foreach ( $input as $key => $text ) { + $result[$key] = wpcf7_normalize_newline_deep( $text, $to ); + } + + return $result; + } + + return wpcf7_normalize_newline( $input, $to ); +} + + +/** + * Strips newline characters. + * + * @param string $text Input text. + * @return string Processed one-line text. + */ +function wpcf7_strip_newline( $text ) { + $text = (string) $text; + $text = str_replace( array( "\r", "\n" ), '', $text ); + return wpcf7_strip_whitespaces( $text ); +} + + +/** + * Canonicalizes text. + * + * @param string $text Input text. + * @param string|array|object $options Options. + * @return string Canonicalized text. + */ +function wpcf7_canonicalize( $text, $options = '' ) { + // for back-compat + if ( + is_string( $options ) and + '' !== $options and + ! str_contains( $options, '=' ) + ) { + $options = array( + 'strto' => $options, + ); + } + + $options = wp_parse_args( $options, array( + 'strto' => 'lower', + 'strip_separators' => false, + ) ); + + static $charset = null; + + if ( ! isset( $charset ) ) { + $charset = get_option( 'blog_charset' ); + + $is_utf8 = in_array( + $charset, + array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ), + true + ); + + if ( $is_utf8 ) { + $charset = 'UTF-8'; + } + } + + $text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, $charset ); + + if ( function_exists( 'mb_convert_kana' ) ) { + $text = mb_convert_kana( $text, 'asKV', $charset ); + } + + if ( $options['strip_separators'] ) { + $text = preg_replace( '/[\r\n\t ]+/', '', $text ); + } else { + $text = preg_replace( '/[\r\n\t ]+/', ' ', $text ); + } + + if ( 'lower' === $options['strto'] ) { + if ( function_exists( 'mb_strtolower' ) ) { + $text = mb_strtolower( $text, $charset ); + } else { + $text = strtolower( $text ); + } + } elseif ( 'upper' === $options['strto'] ) { + if ( function_exists( 'mb_strtoupper' ) ) { + $text = mb_strtoupper( $text, $charset ); + } else { + $text = strtoupper( $text ); + } + } + + return wpcf7_strip_whitespaces( $text ); +} + + +/** + * Returns a canonical keyword usable for a name or an ID purposes. + */ +function wpcf7_canonicalize_name( $text ) { + return preg_replace( '/[^0-9a-z]+/i', '-', $text ); +} + + +/** + * Sanitizes Contact Form 7's form unit-tag. + * + * @param string $tag Unit-tag. + * @return string Sanitized unit-tag. + */ +function wpcf7_sanitize_unit_tag( $tag ) { + $tag = preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $tag ); + return $tag; +} + + +/** + * Converts a file name to one that is not executable as a script. + * + * @param string $filename File name. + * @return string Converted file name. + */ +function wpcf7_antiscript_file_name( $filename ) { + $filename = wp_basename( $filename ); + + // Apply part of protection logic from sanitize_file_name(). + $filename = str_replace( + array( + '?', '[', ']', '/', '\\', '=', '<', '>', ':', ';', ',', "'", '"', + '&', '$', '#', '*', '(', ')', '|', '~', '`', '!', '{', '}', + '%', '+', '’', '«', '»', '”', '“', chr( 0 ) + ), + '', + $filename + ); + + $filename = preg_replace( '/[\r\n\t -]+/', '-', $filename ); + $filename = preg_replace( '/[\pC\pZ]+/iu', '', $filename ); + + $parts = explode( '.', $filename ); + + if ( count( $parts ) < 2 ) { + return $filename; + } + + $script_pattern = '/^(php|phtml|pl|py|rb|cgi|asp|aspx)\d?$/i'; + + $filename = array_shift( $parts ); + $extension = array_pop( $parts ); + + foreach ( $parts as $part ) { + if ( preg_match( $script_pattern, $part ) ) { + $filename .= '.' . $part . '_'; + } else { + $filename .= '.' . $part; + } + } + + if ( preg_match( $script_pattern, $extension ) ) { + $filename .= '.' . $extension . '_.txt'; + } else { + $filename .= '.' . $extension; + } + + return $filename; +} + + +/** + * Masks a password with asterisks (*). + * + * @param int $right Length of right-hand unmasked text. Default 0. + * @param int $left Length of left-hand unmasked text. Default 0. + * @return string Text of masked password. + */ +function wpcf7_mask_password( $text, $right = 0, $left = 0 ) { + $length = strlen( $text ); + + $right = absint( $right ); + $left = absint( $left ); + + if ( $length < $right + $left ) { + $right = $left = 0; + } + + if ( $length <= 48 ) { + $masked = str_repeat( '*', $length - ( $right + $left ) ); + } elseif ( $right + $left < 48 ) { + $masked = str_repeat( '*', 48 - ( $right + $left ) ); + } else { + $masked = '****'; + } + + $left_unmasked = $left ? substr( $text, 0, $left ) : ''; + $right_unmasked = $right ? substr( $text, -1 * $right ) : ''; + + $text = $left_unmasked . $masked . $right_unmasked; + + return $text; +} + + +/** + * Returns an array of allowed HTML tags and attributes for a given context. + * + * @param string $context Context used to decide allowed tags and attributes. + * @return array Array of allowed HTML tags and their allowed attributes. + */ +function wpcf7_kses_allowed_html( $context = 'form' ) { + static $allowed_tags = array(); + + if ( isset( $allowed_tags[$context] ) ) { + return apply_filters( + 'wpcf7_kses_allowed_html', + $allowed_tags[$context], + $context + ); + } + + $allowed_tags[$context] = wp_kses_allowed_html( 'post' ); + + if ( 'form' === $context ) { + $additional_tags_for_form = array( + 'button' => array( + 'disabled' => true, + 'name' => true, + 'type' => true, + 'value' => true, + ), + 'datalist' => array(), + 'fieldset' => array( + 'disabled' => true, + 'name' => true, + ), + 'input' => array( + 'accept' => true, + 'alt' => true, + 'autocomplete' => true, + 'capture' => true, + 'checked' => true, + 'disabled' => true, + 'list' => true, + 'max' => true, + 'maxlength' => true, + 'min' => true, + 'minlength' => true, + 'multiple' => true, + 'name' => true, + 'pattern' => true, + 'placeholder' => true, + 'readonly' => true, + 'required' => true, + 'size' => true, + 'step' => true, + 'type' => true, + 'value' => true, + ), + 'label' => array( + 'for' => true, + ), + 'legend' => array(), + 'meter' => array( + 'value' => true, + 'min' => true, + 'max' => true, + 'low' => true, + 'high' => true, + 'optimum' => true, + ), + 'optgroup' => array( + 'disabled' => true, + 'label' => true, + ), + 'option' => array( + 'disabled' => true, + 'label' => true, + 'selected' => true, + 'value' => true, + ), + 'output' => array( + 'for' => true, + 'name' => true, + ), + 'progress' => array( + 'max' => true, + 'value' => true, + ), + 'select' => array( + 'autocomplete' => true, + 'disabled' => true, + 'multiple' => true, + 'name' => true, + 'required' => true, + 'size' => true, + ), + 'textarea' => array( + 'autocomplete' => true, + 'cols' => true, + 'disabled' => true, + 'maxlength' => true, + 'minlength' => true, + 'name' => true, + 'placeholder' => true, + 'readonly' => true, + 'required' => true, + 'rows' => true, + 'wrap' => true, + ), + ); + + $allowed_tags[$context] = array_merge( + $allowed_tags[$context], + $additional_tags_for_form + ); + + $allowed_tags[$context] = array_map( + static function ( $elm ) { + $global_attributes = array( + 'aria-atomic' => true, + 'aria-checked' => true, + 'aria-controls' => true, + 'aria-current' => true, + 'aria-describedby' => true, + 'aria-details' => true, + 'aria-disabled' => true, + 'aria-expanded' => true, + 'aria-hidden' => true, + 'aria-invalid' => true, + 'aria-label' => true, + 'aria-labelledby' => true, + 'aria-live' => true, + 'aria-relevant' => true, + 'aria-required' => true, + 'aria-selected' => true, + 'class' => true, + 'data-*' => true, + 'dir' => true, + 'hidden' => true, + 'id' => true, + 'inputmode' => true, + 'lang' => true, + 'role' => true, + 'spellcheck' => true, + 'style' => true, + 'tabindex' => true, + 'title' => true, + 'xml:lang' => true, + ); + + return array_merge( $global_attributes, (array) $elm ); + }, + $allowed_tags[$context] + ); + } + + return apply_filters( + 'wpcf7_kses_allowed_html', + $allowed_tags[$context], + $context + ); +} + + +/** + * Sanitizes content for allowed HTML tags for the specified context. + * + * @param string $input Content to filter. + * @param string $context Context used to decide allowed tags and attributes. + * @return string Filtered text with allowed HTML tags and attributes intact. + */ +function wpcf7_kses( $input, $context = 'form' ) { + $output = wp_kses( + $input, + wpcf7_kses_allowed_html( $context ) + ); + + return $output; +} + + +/** + * Returns a formatted string of HTML attributes. + * + * @param array $atts Associative array of attribute name and value pairs. + * @return string Formatted HTML attributes. + */ +function wpcf7_format_atts( $atts ) { + $atts_filtered = array(); + + foreach ( $atts as $name => $value ) { + $name = strtolower( trim( $name ) ); + + if ( ! preg_match( '/^[a-z_:][a-z_:.0-9-]*$/', $name ) ) { + continue; + } + + static $boolean_attributes = array( + 'checked', + 'disabled', + 'inert', + 'multiple', + 'readonly', + 'required', + 'selected', + ); + + if ( in_array( $name, $boolean_attributes, true ) and '' === $value ) { + $value = false; + } + + if ( is_numeric( $value ) ) { + $value = (string) $value; + } + + if ( null === $value or false === $value ) { + unset( $atts_filtered[$name] ); + } elseif ( true === $value ) { + $atts_filtered[$name] = $name; // boolean attribute + } elseif ( is_string( $value ) ) { + $atts_filtered[$name] = trim( $value ); + } + } + + $output = ''; + + foreach ( $atts_filtered as $name => $value ) { + $output .= sprintf( ' %1$s="%2$s"', $name, esc_attr( $value ) ); + } + + return trim( $output ); +} + + +/** + * Returns the regular expression pattern that represents + * whitespace characters Unicode defines. + * + * @link https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt + * + * @return string Regular expression pattern. + */ +function wpcf7_get_unicode_whitespaces() { + return '\x09-\x0D\x20\x85\xA0\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}'; +} + + +/** + * Strips surrounding whitespaces. + * + * @link https://contactform7.com/2024/07/13/consistent-handling-policy-of-surrounding-whitespaces/ + * + * @param string|array $input Input text. + * @param string $side The side from which whitespaces are stripped. + * 'start', 'end', or 'both' (default). + * @return string|array Output text. + */ +function wpcf7_strip_whitespaces( $input, $side = 'both' ) { + if ( is_array( $input ) ) { + return array_map( + static function ( $i ) use ( $side ) { + return wpcf7_strip_whitespaces( $i, $side ); + }, + $input + ); + } + + // https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html + $whitespaces = wpcf7_get_unicode_whitespaces() . '\x{FEFF}'; + + if ( 'end' !== $side ) { + // Strip leading whitespaces + $input = preg_replace( + sprintf( '/^[%s]+/u', $whitespaces ), + '', + $input + ); + } + + if ( 'start' !== $side ) { + // Strip trailing whitespaces + $input = preg_replace( + sprintf( '/[%s]+$/u', $whitespaces ), + '', + $input + ); + } + + return $input; +} diff --git a/wp-content/plugins/contact-form-7/includes/functions.php b/wp-content/plugins/contact-form-7/includes/functions.php new file mode 100644 index 0000000..2ce51cf --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/functions.php @@ -0,0 +1,749 @@ + $uploads['basedir'], + 'url' => $uploads['baseurl'], + ) ); + + if ( 'dir' === $type ) { + return $uploads['dir']; + } if ( 'url' === $type ) { + return $uploads['url']; + } + + return $uploads; +} + + +/** + * Verifies that a correct security nonce was used with time limit. + * + * @param string $nonce Nonce value that was used for verification. + * @param string $action Optional. Context to what is taking place. + * Default 'wp_rest'. + * @return int|bool 1 if the nonce is generated between 0-12 hours ago, + * 2 if the nonce is generated between 12-24 hours ago. + * False if the nonce is invalid. + */ +function wpcf7_verify_nonce( $nonce, $action = 'wp_rest' ) { + return wp_verify_nonce( $nonce, $action ); +} + + +/** + * Creates a cryptographic token tied to a specific action, user, user session, + * and window of time. + * + * @param string $action Optional. Context to what is taking place. + * Default 'wp_rest'. + * @return string The token. + */ +function wpcf7_create_nonce( $action = 'wp_rest' ) { + return wp_create_nonce( $action ); +} + + +/** + * Converts multi-dimensional array to a flat array. + * + * @param mixed $input Array or item of array. + * @return array Flatten array. + */ +function wpcf7_array_flatten( $input ) { + if ( ! is_array( $input ) ) { + return array( $input ); + } + + $output = array(); + + foreach ( $input as $value ) { + $output = array_merge( $output, wpcf7_array_flatten( $value ) ); + } + + return $output; +} + + +/** + * Excludes unset or blank text values from the given array. + * + * @param array $input The array. + * @return array Array without blank text values. + */ +function wpcf7_exclude_blank( $input ) { + $output = array_filter( $input, + static function ( $i ) { + return isset( $i ) && '' !== $i; + } + ); + + return array_values( $output ); +} + + +/** + * Creates a comma-separated list from a multi-dimensional array. + * + * @param mixed $input Array or item of array. + * @param string|array $options Optional. Output options. + * @return string Comma-separated list. + */ +function wpcf7_flat_join( $input, $options = '' ) { + $options = wp_parse_args( $options, array( + 'separator' => ', ', + ) ); + + $input = wpcf7_array_flatten( $input ); + $output = array(); + + foreach ( $input as $value ) { + if ( is_scalar( $value ) ) { + $output[] = trim( (string) $value ); + } + } + + return implode( $options['separator'], $output ); +} + + +/** + * Returns true if HTML5 is supported. + */ +function wpcf7_support_html5() { + return (bool) wpcf7_apply_filters_deprecated( + 'wpcf7_support_html5', + array( true ), + '5.6', + '' + ); +} + + +/** + * Returns true if HTML5 fallback is active. + */ +function wpcf7_support_html5_fallback() { + return (bool) apply_filters( 'wpcf7_support_html5_fallback', false ); +} + + +/** + * Returns true if the Really Simple CAPTCHA plugin is used for contact forms. + */ +function wpcf7_use_really_simple_captcha() { + return apply_filters( 'wpcf7_use_really_simple_captcha', + WPCF7_USE_REALLY_SIMPLE_CAPTCHA + ); +} + + +/** + * Returns true if config validation is active. + */ +function wpcf7_validate_configuration() { + return apply_filters( 'wpcf7_validate_configuration', + WPCF7_VALIDATE_CONFIGURATION + ); +} + + +/** + * Returns true if wpcf7_autop() is applied. + */ +function wpcf7_autop_or_not( $options = '' ) { + $options = wp_parse_args( $options, array( + 'for' => 'form', + ) ); + + return (bool) apply_filters( 'wpcf7_autop_or_not', WPCF7_AUTOP, $options ); +} + + +/** + * Returns true if JavaScript for this plugin is loaded. + */ +function wpcf7_load_js() { + return apply_filters( 'wpcf7_load_js', WPCF7_LOAD_JS ); +} + + +/** + * Returns true if CSS for this plugin is loaded. + */ +function wpcf7_load_css() { + return apply_filters( 'wpcf7_load_css', WPCF7_LOAD_CSS ); +} + + +/** + * Builds an HTML anchor element. + * + * @param string $url Link URL. + * @param string $anchor_text Anchor label text. + * @param string|array $atts Optional. HTML attributes. + * @return string Formatted anchor element. + */ +function wpcf7_link( $url, $anchor_text, $atts = '' ) { + $atts = wp_parse_args( $atts, array( + 'id' => null, + 'class' => null, + ) ); + + $atts = array_merge( $atts, array( + 'href' => esc_url( $url ), + ) ); + + return sprintf( + '%2$s', + wpcf7_format_atts( $atts ), + esc_html( $anchor_text ) + ); +} + + +/** + * Returns the current request URL. + */ +function wpcf7_get_request_uri() { + static $request_uri = ''; + + if ( empty( $request_uri ) ) { + $request_uri = add_query_arg( array() ); + $request_uri = '/' . ltrim( $request_uri, '/' ); + } + + return sanitize_url( $request_uri ); +} + + +/** + * Registers post types used for this plugin. + */ +function wpcf7_register_post_types() { + if ( class_exists( 'WPCF7_ContactForm' ) ) { + WPCF7_ContactForm::register_post_type(); + return true; + } else { + return false; + } +} + + +/** + * Returns the version string of this plugin. + * + * @param string|array $options Optional. Output options. + * @return string Version string. + */ +function wpcf7_version( $options = '' ) { + $options = wp_parse_args( $options, array( + 'limit' => -1, + 'only_major' => false, + ) ); + + if ( $options['only_major'] ) { + $options['limit'] = 2; + } + + $options['limit'] = (int) $options['limit']; + + $ver = WPCF7_VERSION; + $ver = strtr( $ver, '_-+', '...' ); + $ver = preg_replace( '/[^0-9.]+/', '.$0.', $ver ); + $ver = preg_replace( '/[.]+/', '.', $ver ); + $ver = trim( $ver, '.' ); + $ver = explode( '.', $ver ); + + if ( -1 < $options['limit'] ) { + $ver = array_slice( $ver, 0, $options['limit'] ); + } + + $ver = implode( '.', $ver ); + + return $ver; +} + + +/** + * Returns array entries that match the given version. + * + * @param string $version The version to search for. + * @param array $input Search target array. + * @return array|bool Array of matched entries. False on failure. + */ +function wpcf7_version_grep( $version, array $input ) { + $pattern = '/^' . preg_quote( (string) $version, '/' ) . '(?:\.|$)/'; + + return preg_grep( $pattern, $input ); +} + + +/** + * Returns an enctype attribute value. + * + * @param string $enctype Enctype value. + * @return string Enctype value. Empty if not a valid enctype. + */ +function wpcf7_enctype_value( $enctype ) { + $enctype = trim( $enctype ); + + if ( empty( $enctype ) ) { + return ''; + } + + $valid_enctypes = array( + 'application/x-www-form-urlencoded', + 'multipart/form-data', + 'text/plain', + ); + + if ( in_array( $enctype, $valid_enctypes, true ) ) { + return $enctype; + } + + $pattern = '%^enctype="(' . implode( '|', $valid_enctypes ) . ')"$%'; + + if ( preg_match( $pattern, $enctype, $matches ) ) { + return $matches[1]; // for back-compat + } + + return ''; +} + + +/** + * Removes directory recursively. + * + * @param string $dir Directory path. + * @return bool True on success, false on failure. + */ +function wpcf7_rmdir_p( $dir ) { + $filesystem = WPCF7_Filesystem::get_instance(); + + return $filesystem->delete( $dir, true ); +} + + +/** + * Builds a URL-encoded query string. + * + * @link https://developer.wordpress.org/reference/functions/_http_build_query/ + * + * @param array $data URL query parameters. + * @param string $key Optional. If specified, used to prefix key name. + * @return string Query string. + */ +function wpcf7_build_query( $data, $key = '' ) { + $sep = '&'; + $ret = array(); + + foreach ( (array) $data as $k => $v ) { + $k = urlencode( $k ); + + if ( ! empty( $key ) ) { + $k = $key . '%5B' . $k . '%5D'; + } + + if ( null === $v ) { + continue; + } elseif ( false === $v ) { + $v = '0'; + } + + if ( is_array( $v ) or is_object( $v ) ) { + array_push( $ret, wpcf7_build_query( $v, $k ) ); + } else { + array_push( $ret, $k . '=' . urlencode( $v ) ); + } + } + + return implode( $sep, $ret ); +} + + +/** + * Returns the number of code units in a string. + * + * @link http://www.w3.org/TR/html5/infrastructure.html#code-unit-length + * + * @param string $text Input string. + * @return int|false The number of code units, or false if + * mb_convert_encoding is not available. + */ +function wpcf7_count_code_units( $text ) { + static $use_mb = null; + + if ( is_null( $use_mb ) ) { + $use_mb = function_exists( 'mb_convert_encoding' ); + } + + if ( ! $use_mb ) { + return false; + } + + $text = (string) $text; + + if ( '' === $text ) { + return 0; + } + + $text = str_replace( "\r\n", "\n", $text ); + + $text = mb_convert_encoding( + $text, + 'UTF-16', + mb_detect_encoding( $text, mb_detect_order(), true ) ?: 'UTF-8' + ); + + return intdiv( mb_strlen( $text, '8bit' ), 2 ); +} + + +/** + * Returns true if WordPress is running on the localhost. + */ +function wpcf7_is_localhost() { + $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST ); + + return in_array( + strtolower( $sitename ), + array( 'localhost', '127.0.0.1' ), + true + ); +} + + +/** + * Marks a function as deprecated and informs when it has been used. + * + * @param string $function_name The function that was called. + * @param string $version The version of Contact Form 7 that deprecated + * the function. + * @param string $replacement The function that should have been called. + */ +function wpcf7_deprecated_function( $function_name, $version, $replacement ) { + + if ( ! WP_DEBUG ) { + return; + } + + if ( function_exists( '__' ) ) { + /* translators: 1: PHP function name, 2: version number, 3: alternative function name */ + $message = __( 'Function %1$s is deprecated since Contact Form 7 version %2$s! Use %3$s instead.', 'contact-form-7' ); + } else { + $message = 'Function %1$s is deprecated since Contact Form 7 version %2$s! Use %3$s instead.'; + } + + $message = sprintf( $message, $function_name, $version, $replacement ); + + wp_trigger_error( '', $message, E_USER_DEPRECATED ); +} + + +/** + * Fires functions attached to a deprecated filter hook. + * + * @param string $hook_name The name of the filter hook. + * @param array $args Array of additional function arguments to be + * passed to apply_filters(). + * @param string $version The version of Contact Form 7 that deprecated + * the hook. + * @param string $replacement The hook that should have been used. + */ +function wpcf7_apply_filters_deprecated( $hook_name, $args, $version, $replacement = '' ) { + if ( ! has_filter( $hook_name ) ) { + return $args[0]; + } + + if ( WP_DEBUG and apply_filters( 'deprecated_hook_trigger_error', true ) ) { + if ( $replacement ) { + wp_trigger_error( + '', + sprintf( + /* translators: 1: WordPress hook name, 2: version number, 3: alternative hook name */ + __( 'Hook %1$s is deprecated since Contact Form 7 version %2$s! Use %3$s instead.', 'contact-form-7' ), + $hook_name, + $version, + $replacement + ), + E_USER_DEPRECATED + ); + } else { + wp_trigger_error( + '', + sprintf( + /* translators: 1: WordPress hook name, 2: version number */ + __( 'Hook %1$s is deprecated since Contact Form 7 version %2$s with no alternative available.', 'contact-form-7' ), + $hook_name, + $version + ), + E_USER_DEPRECATED + ); + } + } + + return apply_filters_ref_array( $hook_name, $args ); +} + + +/** + * Marks something as being incorrectly called. + * + * @param string $function_name The function that was called. + * @param string $message A message explaining what has been done incorrectly. + * @param string $version The version of Contact Form 7 where the message + * was added. + */ +function wpcf7_doing_it_wrong( $function_name, $message, $version ) { + + if ( ! WP_DEBUG ) { + return; + } + + if ( function_exists( '__' ) ) { + if ( $version ) { + $version = sprintf( + /* translators: %s: Contact Form 7 version number. */ + __( '(This message was added in Contact Form 7 version %s.)', 'contact-form-7' ), + $version + ); + } + + wp_trigger_error( + '', + sprintf( + /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: Contact Form 7 version number. */ + __( 'Function %1$s was called incorrectly. %2$s %3$s', 'contact-form-7' ), + $function_name, + $message, + $version + ), + E_USER_NOTICE + ); + } else { + if ( $version ) { + $version = sprintf( + '(This message was added in Contact Form 7 version %s.)', + $version + ); + } + + wp_trigger_error( + '', + sprintf( + 'Function %1$s was called incorrectly. %2$s %3$s', + $function_name, + $message, + $version + ), + E_USER_NOTICE + ); + } +} + + +/** + * Triggers an error about a remote HTTP request and response. + * + * @param string $url The resource URL. + * @param array $request Request arguments. + * @param array|WP_Error $response The response or WP_Error on failure. + */ +function wpcf7_log_remote_request( $url, $request, $response ) { + + if ( ! WP_DEBUG ) { + return; + } + + $log = sprintf( + /* translators: 1: response code, 2: message, 3: body, 4: URL */ + __( 'HTTP Response: %1$s %2$s %3$s from %4$s', 'contact-form-7' ), + (int) wp_remote_retrieve_response_code( $response ), + wp_remote_retrieve_response_message( $response ), + wp_remote_retrieve_body( $response ), + $url + ); + + $log = apply_filters( 'wpcf7_log_remote_request', + $log, $url, $request, $response + ); + + if ( $log ) { + wp_trigger_error( '', $log, E_USER_NOTICE ); + } +} + + +/** + * Anonymizes an IP address by masking local part. + * + * @param string $ip_addr The original IP address. + * @return string|bool Anonymized IP address, or false on failure. + */ +function wpcf7_anonymize_ip_addr( $ip_addr ) { + if ( + ! function_exists( 'inet_ntop' ) or + ! function_exists( 'inet_pton' ) + ) { + return $ip_addr; + } + + $packed = inet_pton( $ip_addr ); + + if ( false === $packed ) { + return $ip_addr; + } + + if ( 4 === strlen( $packed ) ) { // IPv4 + $mask = '255.255.255.0'; + } elseif ( 16 === strlen( $packed ) ) { // IPv6 + $mask = 'ffff:ffff:ffff:0000:0000:0000:0000:0000'; + } else { + return $ip_addr; + } + + return inet_ntop( $packed & inet_pton( $mask ) ); +} + + +/** + * Retrieves a sanitized value from the $_GET superglobal. + * + * @param string $key Array key. + * @param mixed $default The default value returned when + * the specified superglobal is not set. + * @return mixed Sanitized value. + */ +function wpcf7_superglobal_get( $key, $default = '' ) { + return wpcf7_superglobal( 'get', $key ) ?? $default; +} + + +/** + * Retrieves a sanitized value from the $_POST superglobal. + * + * @param string $key Array key. + * @param mixed $default The default value returned when + * the specified superglobal is not set. + * @return mixed Sanitized value. + */ +function wpcf7_superglobal_post( $key, $default = '' ) { + return wpcf7_superglobal( 'post', $key ) ?? $default; +} + + +/** + * Retrieves a sanitized value from the $_REQUEST superglobal. + * + * @param string $key Array key. + * @param mixed $default The default value returned when + * the specified superglobal is not set. + * @return mixed Sanitized value. + */ +function wpcf7_superglobal_request( $key, $default = '' ) { + return wpcf7_superglobal( 'request', $key ) ?? $default; +} + + +/** + * Retrieves a sanitized value from the $_SERVER superglobal. + * + * @param string $key Array key. + * @param mixed $default The default value returned when + * the specified superglobal is not set. + * @return mixed Sanitized value. + */ +function wpcf7_superglobal_server( $key, $default = '' ) { + return wpcf7_superglobal( 'server', $key ) ?? $default; +} + + +/** + * Retrieves a sanitized value from the specified superglobal. + * + * @param string $superglobal A superglobal type. + * @param string $key Array key. + * @return string|array|null Sanitized value. + */ +function wpcf7_superglobal( $superglobal, $key ) { + $superglobals = array( + 'get' => $_GET, + 'post' => $_POST, + 'request' => $_REQUEST, + 'server' => $_SERVER, + ); + + if ( isset( $superglobals[$superglobal][$key] ) ) { + return map_deep( + $superglobals[$superglobal][$key], + static function ( $val ) { + $val = wp_unslash( $val ); + $val = wp_check_invalid_utf8( $val ); + $val = wp_kses_no_null( $val ); + $val = wpcf7_strip_whitespaces( $val ); + return $val; + } + ); + } +} diff --git a/wp-content/plugins/contact-form-7/includes/html-formatter.php b/wp-content/plugins/contact-form-7/includes/html-formatter.php new file mode 100644 index 0000000..5f08d8d --- /dev/null +++ b/wp-content/plugins/contact-form-7/includes/html-formatter.php @@ -0,0 +1,868 @@ +options = wp_parse_args( $options, array( + 'auto_br' => true, + 'auto_indent' => true, + 'allowed_html' => wpcf7_kses_allowed_html(), + ) ); + } + + + /** + * Separates the given text into chunks of HTML. Each chunk must be an + * associative array that includes 'position', 'type', and 'content' keys. + * + * @param string $input Text to be separated into chunks. + * @return iterable Iterable of chunks. + */ + public function separate_into_chunks( $input ) { + $input_bytelength = strlen( $input ); + $position = 0; + + while ( $position < $input_bytelength ) { + $next_tag = preg_match( + '/(?:|<(?:\/?)[a-z].*?>)/is', + $input, + $matches, + PREG_OFFSET_CAPTURE, + $position + ); + + if ( ! $next_tag ) { + yield array( + 'position' => $position, + 'type' => self::text, + 'content' => substr( $input, $position ), + ); + + break; + } + + $next_tag = $matches[0][0]; + $next_tag_position = $matches[0][1]; + + if ( $position < $next_tag_position ) { + yield array( + 'position' => $position, + 'type' => self::text, + 'content' => substr( + $input, + $position, + $next_tag_position - $position + ), + ); + } + + if ( ' $next_tag_position, + 'type' => $next_tag_type, + 'content' => substr( + $input, + $next_tag_position, + strlen( $next_tag ) + ), + ); + + $position = $next_tag_position + strlen( $next_tag ); + } + } + + + /** + * Normalizes content in each chunk. This may change the type and position + * of the chunk. + * + * @param iterable $chunks The original chunks. + * @return iterable Normalized chunks. + */ + public function pre_format( $chunks ) { + $position = 0; + + foreach ( $chunks as $chunk ) { + $chunk['position'] = $position; + + // Standardize newline characters to "\n". + $chunk['content'] = str_replace( + array( "\r\n", "\r" ), "\n", $chunk['content'] + ); + + if ( self::start_tag === $chunk['type'] ) { + list( $chunk['content'] ) = + self::normalize_start_tag( $chunk['content'] ); + + // Replace
    by a line break. + if ( + $this->options['auto_br'] and + preg_match( '/^$/i', $chunk['content'] ) + ) { + $chunk['type'] = self::text; + $chunk['content'] = "\n"; + } + } + + yield $chunk; + $position = self::calc_next_position( $chunk ); + } + } + + + /** + * Concatenates neighboring text chunks to create a single chunk. + * + * @param iterable $chunks The original chunks. + * @return iterable Processed chunks. + */ + public function concatenate_texts( $chunks ) { + $position = 0; + $text_left = null; + + foreach ( $chunks as $chunk ) { + $chunk['position'] = $position; + + if ( self::text === $chunk['type'] ) { + if ( isset( $text_left ) ) { + $text_left['content'] .= $chunk['content']; + } else { + $text_left = $chunk; + } + + continue; + } + + if ( isset( $text_left ) ) { + yield $text_left; + $chunk['position'] = self::calc_next_position( $text_left ); + $text_left = null; + } + + yield $chunk; + $position = self::calc_next_position( $chunk ); + } + + if ( isset( $text_left ) ) { + yield $text_left; + } + } + + + /** + * Outputs formatted HTML based on the given chunks. + * + * @param iterable $chunks The original chunks. + * @return string Formatted HTML. + */ + public function format( $chunks ) { + $chunks = $this->pre_format( $chunks ); + $chunks = $this->concatenate_texts( $chunks ); + + $this->output = ''; + $this->stacked_elements = array(); + + foreach ( $chunks as $chunk ) { + + if ( self::text === $chunk['type'] ) { + $this->append_text( $chunk['content'] ); + } + + if ( self::start_tag === $chunk['type'] ) { + $this->start_tag( $chunk['content'] ); + } + + if ( self::end_tag === $chunk['type'] ) { + $this->end_tag( $chunk['content'] ); + } + + if ( self::comment === $chunk['type'] ) { + $this->append_comment( $chunk['content'] ); + } + } + + return $this->output(); + } + + + /** + * Appends preformatted text to the output property. + */ + public function append_preformatted( $content ) { + $this->output .= $content; + } + + + /** + * Appends whitespace to the output property. + */ + public function append_whitespace() { + $this->append_preformatted( ' ' ); + } + + + /** + * Appends a text node content to the output property. + * + * @param string $content Text node content. + */ + public function append_text( $content ) { + if ( $this->is_inside( array( 'pre', 'template' ) ) ) { + $this->append_preformatted( $content ); + return; + } + + if ( + empty( $this->stacked_elements ) or + $this->has_parent( 'p' ) or + $this->has_parent( self::p_parent_elements ) + ) { + // Close

    if the content starts with multiple line breaks. + if ( preg_match( '/^\s*\n\s*\n\s*/', $content ) ) { + $this->end_tag( 'p' ); + } + + // Split up the contents into paragraphs, separated by double line breaks. + $paragraphs = preg_split( '/\s*\n\s*\n\s*/', $content ); + + $paragraphs = array_filter( $paragraphs, static function ( $paragraph ) { + return '' !== wpcf7_strip_whitespaces( $paragraph ); + } ); + + $paragraphs = array_values( $paragraphs ); + + if ( $paragraphs ) { + if ( $this->is_inside( 'p' ) ) { + $paragraph = array_shift( $paragraphs ); + + $paragraph = self::normalize_paragraph( + $paragraph, + $this->options['auto_br'] + ); + + $this->append_preformatted( $paragraph ); + } + + foreach ( $paragraphs as $paragraph ) { + $this->start_tag( 'p' ); + + $paragraph = wpcf7_strip_whitespaces( $paragraph, 'start' ); + + $paragraph = self::normalize_paragraph( + $paragraph, + $this->options['auto_br'] + ); + + $this->append_preformatted( $paragraph ); + } + } + + // Close

    if the content ends with multiple line breaks. + if ( preg_match( '/\s*\n\s*\n\s*$/', $content ) ) { + $this->end_tag( 'p' ); + } + + // Cases where the content is a single line break. + if ( preg_match( '/^\s*\n\s*$/', $content ) ) { + $auto_br = $this->options['auto_br'] && $this->is_inside( 'p' ); + + $content = self::normalize_paragraph( $content, $auto_br ); + + $this->append_preformatted( $content ); + } + } else { + $auto_br = $this->options['auto_br'] && + $this->has_parent( self::br_parent_elements ); + + $content = self::normalize_paragraph( $content, $auto_br ); + + $this->append_preformatted( $content ); + } + } + + + /** + * Appends a start tag to the output property. + * + * @param string $tag A start tag. + */ + public function start_tag( $tag ) { + list( $tag, $tag_name ) = self::normalize_start_tag( $tag ); + + if ( + in_array( $tag_name, self::p_child_elements, true ) and + ! $this->is_inside( 'p' ) and + ! $this->is_inside( self::p_child_elements ) and + ! $this->has_parent( self::p_nonparent_elements ) + ) { + // Open

    if it does not exist. + $this->start_tag( 'p' ); + } + + $this->append_start_tag( $tag_name, array(), $tag ); + } + + + /** + * Appends a start tag to the output property. + * + * @param string $tag_name Tag name. + * @param array $atts Associative array of attribute name and value pairs. + * @param string $tag A start tag. + */ + public function append_start_tag( $tag_name, $atts = array(), $tag = '' ) { + if ( ! self::validate_tag_name( $tag_name ) ) { + wp_trigger_error( + __METHOD__, + sprintf( + /* translators: %s: Invalid HTML tag name */ + __( 'Invalid tag name (%s) is specified.', 'contact-form-7' ), + $tag_name + ), + E_USER_WARNING + ); + + return false; + } + + if ( WP_DEBUG and ! empty( $this->options['allowed_html'] ) ) { + $html_disallowance = array(); + + if ( ! isset( $this->options['allowed_html'][$tag_name] ) ) { + $html_disallowance = array( + 'element' => $tag_name, + ); + } else { + $atts_allowed = $this->options['allowed_html'][$tag_name]; + + $atts_disallowed = array_diff_ukey( $atts, $atts_allowed, + static function ( $key_1, $key_2 ) use ( $atts_allowed ) { + if ( + str_starts_with( $key_1, 'data-' ) and + ! empty( $atts_allowed['data-*'] ) and + preg_match( '/^data-[a-z0-9_-]+$/', $key_1 ) + ) { + return 0; + } else { + return $key_1 === $key_2 ? 0 : 1; + } + } + ); + + if ( ! empty( $atts_disallowed ) ) { + $html_disallowance = array( + 'element' => $tag_name, + 'attributes' => array_keys( $atts_disallowed ), + ); + } + } + + if ( $html_disallowance ) { + $notice = sprintf( + /* translators: %s: JSON-formatted array of disallowed HTML */ + __( 'HTML Disallowance: %s', 'contact-form-7' ), + wp_json_encode( $html_disallowance, JSON_PRETTY_PRINT ) + ); + + wp_trigger_error( __METHOD__, $notice, E_USER_NOTICE ); + } + } + + if ( + 'p' === $tag_name or + in_array( $tag_name, self::p_parent_elements, true ) or + in_array( $tag_name, self::p_nonparent_elements, true ) + ) { + // Close

    if it exists. + $this->end_tag( 'p' ); + } + + if ( 'dd' === $tag_name or 'dt' === $tag_name ) { + // Close

    and
    if closing tag is omitted. + $this->end_tag( 'dd' ); + $this->end_tag( 'dt' ); + } + + if ( 'li' === $tag_name ) { + // Close
  • if closing tag is omitted. + $this->end_tag( 'li' ); + } + + if ( 'optgroup' === $tag_name ) { + // Close if closing tag is omitted. + $this->end_tag( 'option' ); + $this->end_tag( 'optgroup' ); + } + + if ( 'option' === $tag_name ) { + // Close
  • %3$s%4$s
  • %6$s', + esc_attr( implode( ' ', $element->classes ) ), + $link_atts, + $element->flag, + $label, + 'discard' === $args['item_spacing'] ? '' : "\t", + 'discard' === $args['item_spacing'] ? '' : "\n" + ); + } + + /** + * Overrides Walker:walk to set depth argument + * + * @since 1.2 + * @since 2.6.7 Use $max_depth and ...$args parameters to follow the move of WP 5.3 + * + * @param array $elements An array of elements. + * @param int $max_depth The maximum hierarchical depth. + * @param mixed ...$args Additional arguments. + * @return string The hierarchical item output. + */ + public function walk( $elements, $max_depth, ...$args ) { // phpcs:ignore WordPressVIPMinimum.Classes.DeclarationCompatibility.DeclarationCompatibility + $this->maybe_fix_walk_args( $max_depth, $args ); + + return parent::walk( $elements, $max_depth, $args ); + } +} diff --git a/wp-content/plugins/polylang/include/walker.php b/wp-content/plugins/polylang/include/walker.php new file mode 100644 index 0000000..cfc6b62 --- /dev/null +++ b/wp-content/plugins/polylang/include/walker.php @@ -0,0 +1,76 @@ + 'parent', 'id' => 'id' ); + + /** + * Overrides Walker::display_element as it expects an object with a parent property. + * + * @since 1.2 + * @since 3.4 Refactored and moved in `PLL_Walker`. + * + * @param PLL_Language|stdClass $element Data object. `PLL_language` in our case. + * @param array $children_elements List of elements to continue traversing. + * @param int $max_depth Max depth to traverse. + * @param int $depth Depth of current element. + * @param array $args An array of arguments. + * @param string $output Passed by reference. Used to append additional content. + * @return void + */ + public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { + if ( $element instanceof PLL_Language ) { + $element = $element->to_std_class(); + + // Sets the w3c locale as the main locale. + $element->locale = $element->w3c ?? $element->locale; + } + + $element->parent = $element->id = 0; // Don't care about this. + + parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output ); + } + + /** + * Sets `PLL_Walker::walk()` arguments as it should + * and triggers an error in case of misuse of them. + * + * @since 3.4 + * + * @param array|int $max_depth The maximum hierarchical depth. Passed by reference. + * @param array $args Additional arguments. Passed by reference. + * @return void + */ + protected function maybe_fix_walk_args( &$max_depth, &$args ) { + if ( ! is_array( $max_depth ) ) { + $args = $args[0] ?? array(); + return; + } + + // Backward compatibility with Polylang < 2.6.7 + _doing_it_wrong( + self::class . '::walk()', + 'The method expects an integer as second parameter.', + '2.6.7' + ); + $args = $max_depth; + $max_depth = -1; + } +} diff --git a/wp-content/plugins/polylang/include/widget-calendar.php b/wp-content/plugins/polylang/include/widget-calendar.php new file mode 100644 index 0000000..8f5b6f3 --- /dev/null +++ b/wp-content/plugins/polylang/include/widget-calendar.php @@ -0,0 +1,355 @@ + not very efficient (add 4 to 5 sql queries). + * Method used since 0.5: remove the WP widget and replace it by our own -> our language filter will not work if get_calendar is called directly by a theme. + * + * @since 0.5 + */ +class PLL_Widget_Calendar extends WP_Widget_Calendar { + protected static $pll_instance = 0; // Can't use $instance of WP_Widget_Calendar as it's private :/. + + /** + * Outputs the content for the current Calendar widget instance. + * Modified version of the parent function to call our own get_calendar() method. + * + * @since 0.5 + * + * @param array $args Display arguments including 'before_title', 'after_title', + * 'before_widget', and 'after_widget'. + * @param array $instance The settings for the particular instance of the widget. + */ + public function widget( $args, $instance ) { + $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; + + /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ + $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); + + echo $args['before_widget']; + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; + } + if ( 0 === self::$pll_instance ) { #modified# + echo '
    '; + } else { + echo '
    '; + } + empty( PLL()->curlang ) ? get_calendar() : self::get_calendar(); #modified# + echo '
    '; + echo $args['after_widget']; + + ++self::$pll_instance; #modified# + } + + /** + * Modified version of the WP `get_calendar()` function to filter the queries. + * + * @since 0.5 + * @since 3.8 New argument $args added, with backward compatibility (WP 6.8). + * + * @param array $args { + * Optional. Arguments for the `get_calendar` function. + * + * @type bool $initial Whether to use initial calendar names. Default true. + * @type bool $display Whether to display the calendar output. Default true. + * @type string $post_type Optional. Post type. Default 'post'. + * } + * @return void|string Void if `$display` argument is true, calendar HTML if `$display` is false. + */ + static function get_calendar( $args = array() ) { + global $wpdb, $m, $monthnum, $year, $wp_locale, $posts; + + $defaults = array( + 'initial' => true, + 'display' => true, + 'post_type' => 'post', + ); + + $original_args = func_get_args(); + $args = array(); + + if ( ! empty( $original_args ) ) { + if ( ! is_array( $original_args[0] ) ) { + if ( isset( $original_args[0] ) && is_bool( $original_args[0] ) ) { + $defaults['initial'] = $original_args[0]; + } + if ( isset( $original_args[1] ) && is_bool( $original_args[1] ) ) { + $defaults['display'] = $original_args[1]; + } + } else { + $args = $original_args[0]; + } + } + + /** This filter is documented in wp-includes/general-template.php */ + $args = apply_filters( 'get_calendar_args', wp_parse_args( $args, $defaults ) ); + + $args['lang'] = PLL()->curlang->slug; #added# + + if ( ! post_type_exists( $args['post_type'] ) ) { + $args['post_type'] = 'post'; + } + + $w = 0; + if ( isset( $_GET['w'] ) ) { + $w = (int) $_GET['w']; + } + + /* + * Normalize the cache key. + * + * The following ensures the same cache key is used for the same parameter + * and parameter equivalents. This prevents `post_type > post, initial > true` + * from generating a different key from the same values in the reverse order. + * + * `display` is excluded from the cache key as the cache contains the same + * HTML regardless of this function's need to echo or return the output. + * + * The global values contain data generated by the URL query string variables. + */ + $cache_args = $args; + unset( $cache_args['display'] ); + + $cache_args['globals'] = array( + 'm' => $m, + 'monthnum' => $monthnum, + 'year' => $year, + 'week' => $w, + ); + + wp_recursive_ksort( $cache_args ); + $key = md5( serialize( $cache_args ) ); + $cache = wp_cache_get( 'get_calendar', 'calendar' ); + + if ( $cache && is_array( $cache ) && isset( $cache[ $key ] ) ) { + /** This filter is documented in wp-includes/general-template.php */ + $output = apply_filters( 'get_calendar', $cache[ $key ], $args ); + + if ( $args['display'] ) { + echo $output; + return; + } + + return $output; + } + + if ( ! is_array( $cache ) ) { + $cache = array(); + } + + $post_type = $args['post_type']; + + // Quick check. If we have no posts at all, abort! + if ( ! $posts ) { + $prepared_query = $wpdb->prepare( "SELECT 1 as test FROM $wpdb->posts WHERE post_type = %s AND post_status = 'publish' LIMIT 1", $post_type ); + $gotsome = $wpdb->get_var( $prepared_query ); + if ( ! $gotsome ) { + $cache[ $key ] = ''; + wp_cache_set( 'get_calendar', $cache, 'calendar' ); + return; + } + } + + // week_begins = 0 stands for Sunday. + $week_begins = (int) get_option( 'start_of_week' ); + + // Let's figure out when we are. + if ( ! empty( $monthnum ) && ! empty( $year ) ) { + $thismonth = zeroise( (int) $monthnum, 2 ); + $thisyear = (int) $year; + } elseif ( ! empty( $w ) ) { + // We need to get the month from MySQL. + $thisyear = (int) substr( $m, 0, 4 ); + // It seems MySQL's weeks disagree with PHP's. + $d = ( ( $w - 1 ) * 7 ) + 6; + $thismonth = $wpdb->get_var( "SELECT DATE_FORMAT((DATE_ADD('{$thisyear}0101', INTERVAL $d DAY) ), '%m')" ); + } elseif ( ! empty( $m ) ) { + $thisyear = (int) substr( $m, 0, 4 ); + if ( strlen( $m ) < 6 ) { + $thismonth = '01'; + } else { + $thismonth = zeroise( (int) substr( $m, 4, 2 ), 2 ); + } + } else { + $thisyear = current_time( 'Y' ); + $thismonth = current_time( 'm' ); + } + + $unixmonth = mktime( 0, 0, 0, $thismonth, 1, $thisyear ); + $last_day = gmdate( 't', $unixmonth ); + + $join_clause = PLL()->model->post->join_clause(); #added# + $where_clause = PLL()->model->post->where_clause( PLL()->curlang ); #added# + + // Get the next and previous month and year with at least one post. + $previous_prepared_query = $wpdb->prepare( + "SELECT MONTH(post_date) AS month, YEAR(post_date) AS year + FROM $wpdb->posts $join_clause + WHERE post_date < '$thisyear-$thismonth-01' + AND post_type = %s AND post_status = 'publish' $where_clause + ORDER BY post_date DESC + LIMIT 1", + $post_type + ); #modified# + $previous = $wpdb->get_row( $previous_prepared_query ); + + $next_prepared_query = $wpdb->prepare( + "SELECT MONTH(post_date) AS month, YEAR(post_date) AS year + FROM $wpdb->posts $join_clause + WHERE post_date > '$thisyear-$thismonth-{$last_day} 23:59:59' + AND post_type = %s AND post_status = 'publish' $where_clause + ORDER BY post_date ASC + LIMIT 1", + $post_type + ); #modified# + $next = $wpdb->get_row( $next_prepared_query ); + + /* translators: Calendar caption: 1: Month name, 2: 4-digit year. */ + $calendar_caption = _x( '%1$s %2$s', 'calendar caption' ); + $calendar_output = ' + + + '; + + $myweek = array(); + + for ( $wdcount = 0; $wdcount <= 6; $wdcount++ ) { + $myweek[] = $wp_locale->get_weekday( ( $wdcount + $week_begins ) % 7 ); + } + + foreach ( $myweek as $wd ) { + $day_name = $args['initial'] ? $wp_locale->get_weekday_initial( $wd ) : $wp_locale->get_weekday_abbrev( $wd ); + $wd = esc_attr( $wd ); + $calendar_output .= "\n\t\t"; + } + + $calendar_output .= ' + + + + '; + + $daywithpost = array(); + + // Get days with posts. + $dayswithposts_prepared_query = $wpdb->prepare( + "SELECT DISTINCT DAYOFMONTH(post_date) + FROM $wpdb->posts $join_clause WHERE post_date >= '{$thisyear}-{$thismonth}-01 00:00:00' + AND post_type = %s AND post_status = 'publish' + AND post_date <= '{$thisyear}-{$thismonth}-{$last_day} 23:59:59' $where_clause", + $post_type + ); #modified# + $dayswithposts = $wpdb->get_results( $dayswithposts_prepared_query, ARRAY_N ); + + if ( $dayswithposts ) { + foreach ( (array) $dayswithposts as $daywith ) { + $daywithpost[] = (int) $daywith[0]; + } + } + + // See how much we should pad in the beginning. + $pad = calendar_week_mod( (int) gmdate( 'w', $unixmonth ) - $week_begins ); + if ( 0 != $pad ) { + $calendar_output .= "\n\t\t" . ''; + } + + $newrow = false; + $daysinmonth = (int) gmdate( 't', $unixmonth ); + + for ( $day = 1; $day <= $daysinmonth; ++$day ) { + if ( isset( $newrow ) && $newrow ) { + $calendar_output .= "\n\t\n\t\n\t\t"; + } + $newrow = false; + + if ( current_time( 'j' ) == $day && + current_time( 'm' ) == $thismonth && + current_time( 'Y' ) == $thisyear ) { + $calendar_output .= ''; + + if ( 6 == calendar_week_mod( (int) gmdate( 'w', mktime( 0, 0, 0, $thismonth, $day, $thisyear ) ) - $week_begins ) ) { + $newrow = true; + } + } + + $pad = 7 - calendar_week_mod( (int) gmdate( 'w', mktime( 0, 0, 0, $thismonth, $day, $thisyear ) ) - $week_begins ); + if ( 0 != $pad && 7 != $pad ) { + $calendar_output .= "\n\t\t" . ''; + } + + $calendar_output .= "\n\t\n\t"; + + $calendar_output .= "\n\t
    ' . sprintf( + $calendar_caption, + $wp_locale->get_month( $thismonth ), + gmdate( 'Y', $unixmonth ) + ) . '
    $day_name
     
    '; + } else { + $calendar_output .= ''; + } + + if ( in_array( $day, $daywithpost, true ) ) { + // Any posts today? + $date_format = gmdate( _x( 'F j, Y', 'daily archives date format' ), strtotime( "{$thisyear}-{$thismonth}-{$day}" ) ); + /* translators: Post calendar label. %s: Date. */ + $label = sprintf( __( 'Posts published on %s' ), $date_format ); + $calendar_output .= sprintf( + '%s', + get_day_link( $thisyear, $thismonth, $day ), + esc_attr( $label ), + $day + ); + } else { + $calendar_output .= $day; + } + + $calendar_output .= ' 
    "; + + $calendar_output .= ''; + + $cache[ $key ] = $calendar_output; + wp_cache_set( 'get_calendar', $cache, 'calendar' ); + + /** This filter is documented in wp-includes/general-template.php */ + $calendar_output = apply_filters( 'get_calendar', $calendar_output, $args ); + + if ( $args['display'] ) { + echo $calendar_output; + return; + } + + return $calendar_output; + } +} diff --git a/wp-content/plugins/polylang/include/widget-languages.php b/wp-content/plugins/polylang/include/widget-languages.php new file mode 100644 index 0000000..d82880a --- /dev/null +++ b/wp-content/plugins/polylang/include/widget-languages.php @@ -0,0 +1,174 @@ + + * @phpstan-template T of array{ + * title: string, + * dropdown: 0|1, + * show_names: 0|1, + * show_flags: 0|1, + * force_home: 0|1, + * hide_current: 0|1, + * hide_if_no_translation: 0|1 + * } + */ +class PLL_Widget_Languages extends WP_Widget { + + /** + * Constructor + * + * @since 0.1 + */ + public function __construct() { + parent::__construct( + 'polylang', + __( 'Language switcher', 'polylang' ), + array( + 'description' => __( 'Displays a language switcher', 'polylang' ), + 'customize_selective_refresh' => true, + ) + ); + } + + /** + * Displays the widget + * + * @since 0.1 + * + * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget. + * @param array $instance The settings for the particular instance of the widget + * @return void + * + * @phpstan-param array{ + * name: string, + * id: string, + * description: string, + * class: string, + * before_widget: string, + * after_widget: string, + * before_title: string, + * after_title: string, + * before_sidebar: string, + * after_sidebar: string, + * show_in_rest: boolean, + * widget_id: string, + * widget_name: string + * } $args + * @phpstan-param T $instance + */ + public function widget( $args, $instance ) { + // Sets a unique id for dropdown. + $instance['dropdown'] = empty( $instance['dropdown'] ) ? 0 : $this->id; + $instance['echo'] = 0; + $instance['raw'] = 0; + $list = pll_the_languages( $instance ); + + if ( $list ) { + $title = empty( $instance['title'] ) ? '' : $instance['title']; + + /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ + $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput + + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput + } + + // The title may be filtered: Strip out HTML and make sure the aria-label is never empty. + $aria_label = trim( wp_strip_all_tags( $title ) ); + if ( ! $aria_label ) { + $aria_label = __( 'Choose a language', 'polylang' ); + } + + if ( $instance['dropdown'] ) { + echo ''; + echo $list; // phpcs:ignore WordPress.Security.EscapeOutput + } else { + $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml'; + + /** This filter is documented in wp-includes/widgets/class-wp-nav-menu-widget.php */ + $format = apply_filters( 'navigation_widgets_format', $format ); + + if ( 'html5' === $format ) { + echo ''; + } + } + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Updates the widget options + * + * @since 0.4 + * + * @param array $new_instance New settings for this instance as input by the user via form() + * @param array $old_instance Old settings for this instance + * @return array Settings to save or bool false to cancel saving + * + * @phpstan-param T $new_instance + * @phpstan-param T $old_instance + */ + public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $instance = array( 'title' => sanitize_text_field( $new_instance['title'] ) ); + foreach ( array_keys( PLL_Switcher::get_switcher_options( 'widget' ) ) as $key ) { + $instance[ $key ] = ! empty( $new_instance[ $key ] ) ? 1 : 0; + } + + return $instance; + } + + /** + * Displays the widget form. + * + * @since 0.4 + * + * @param array $instance Current settings. + * @return string + * + * @phpstan-param T $instance + */ + public function form( $instance ) { + // Default values + $instance = wp_parse_args( (array) $instance, array_merge( array( 'title' => '' ), PLL_Switcher::get_switcher_options( 'widget', 'default' ) ) ); + + // Title + printf( + '

    ', + esc_attr( $this->get_field_id( 'title' ) ), + esc_html__( 'Title:', 'polylang' ), + esc_attr( $this->get_field_name( 'title' ) ), + esc_attr( $instance['title'] ) + ); + + foreach ( PLL_Switcher::get_switcher_options( 'widget' ) as $key => $str ) { + printf( + '
    ', + esc_attr( $this->get_field_id( $key ) ), + esc_attr( $this->get_field_name( $key ) ), + checked( $instance[ $key ], true, false ), + esc_html( $str ), + in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? sprintf( ' class="no-dropdown-%s"', esc_attr( $this->id ) ) : '', + ( ! empty( $instance['dropdown'] ) && in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? ' style="display:none;"' : '' ), + esc_attr( 'pll-' . $key ) + ); + } + + return ''; // Because the parent class returns a string, however not used. + } +} diff --git a/wp-content/plugins/polylang/install/install-base.php b/wp-content/plugins/polylang/install/install-base.php new file mode 100644 index 0000000..eb2b2b6 --- /dev/null +++ b/wp-content/plugins/polylang/install/install-base.php @@ -0,0 +1,134 @@ +plugin_basename = $plugin_basename; + + // Manages plugin activation and deactivation + register_activation_hook( $plugin_basename, array( $this, 'activate' ) ); + register_deactivation_hook( $plugin_basename, array( $this, 'deactivate' ) ); + + // Site creation on multisite. + add_action( 'wp_initialize_site', array( $this, 'new_site' ), 50 ); // After WP (prio 10). + } + + /** + * Allows to detect plugin deactivation + * + * @since 1.7 + * + * @return bool true if the plugin is currently being deactivated + */ + public function is_deactivation() { + return isset( $_GET['action'], $_GET['plugin'] ) && 'deactivate' === $_GET['action'] && $this->plugin_basename === $_GET['plugin']; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Activation or deactivation for all blogs. + * + * @since 1.2 + * + * @param string $what Either 'activate' or 'deactivate'. + * @param bool $networkwide Whether the plugin is (de)activated for all sites in the network or just the current site. + * @return void + */ + protected function do_for_all_blogs( $what, $networkwide ) { + // Network + if ( is_multisite() && $networkwide ) { + global $wpdb; + + foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { + switch_to_blog( $blog_id ); + 'activate' == $what ? $this->_activate() : $this->_deactivate(); + } + restore_current_blog(); + } + + // Single blog + else { + 'activate' == $what ? $this->_activate() : $this->_deactivate(); + } + } + + /** + * Plugin activation for multisite. + * + * @since 1.7 + * + * @param bool $networkwide Whether the plugin is activated for all sites in the network or just the current site. + * @return void + */ + public function activate( $networkwide ) { + $this->do_for_all_blogs( 'activate', $networkwide ); + } + + /** + * Plugin activation + * + * @since 0.5 + * + * @return void + */ + protected function _activate() { + // Can be overridden in child class + } + + /** + * Plugin deactivation for multisite. + * + * @since 0.1 + * + * @param bool $networkwide Whether the plugin is deactivated for all sites in the network or just the current site. + * @return void + */ + public function deactivate( $networkwide ) { + $this->do_for_all_blogs( 'deactivate', $networkwide ); + } + + /** + * Plugin deactivation + * + * @since 0.5 + * + * @return void + */ + protected function _deactivate() { + // Can be overridden in child class + } + + /** + * Site creation on multisite ( to set default options ) + * + * @since 2.6.8 + * + * @param WP_Site $new_site New site object. + * @return void + */ + public function new_site( $new_site ) { + switch_to_blog( $new_site->id ); + $this->_activate(); + restore_current_blog(); + } +} diff --git a/wp-content/plugins/polylang/install/install.php b/wp-content/plugins/polylang/install/install.php new file mode 100644 index 0000000..6d47e31 --- /dev/null +++ b/wp-content/plugins/polylang/install/install.php @@ -0,0 +1,135 @@ +

    %s

    ', + sprintf( + /* translators: 1: Plugin name 2: Current PHP version 3: Required PHP version */ + esc_html__( '%1$s has deactivated itself because you are using an old version of PHP. You are using using PHP %2$s. %1$s requires PHP %3$s.', 'polylang' ), + esc_html( POLYLANG ), + PHP_VERSION, + esc_html( PLL_MIN_PHP_VERSION ) + ) + ); + } + + /** + * Displays a notice if WP min version is not met. + * + * @since 2.6.7 + * + * @return void + */ + public function wp_version_notice() { + global $wp_version; + + load_plugin_textdomain( 'polylang' ); // Plugin i18n. + + printf( + '

    %s

    ', + sprintf( + /* translators: 1: Plugin name 2: Current WordPress version 3: Required WordPress version */ + esc_html__( '%1$s has deactivated itself because you are using an old version of WordPress. You are using using WordPress %2$s. %1$s requires at least WordPress %3$s.', 'polylang' ), + esc_html( POLYLANG ), + esc_html( $wp_version ), + esc_html( PLL_MIN_WP_VERSION ) + ) + ); + } + + /** + * Plugin activation + * + * @since 0.5 + * + * @return void + */ + protected function _activate() { + add_action( 'pll_init_options_for_blog', array( Options_Registry::class, 'register' ) ); + $options = new Options(); + + if ( ! empty( $options['version'] ) ) { + // Check if we will be able to upgrade. + if ( version_compare( $options['version'], POLYLANG_VERSION, '<' ) ) { + ( new PLL_Upgrade( $options ) )->can_activate(); + } + } else { + $options['version'] = POLYLANG_VERSION; + } + + $options->save(); // Force save here to prevent any conflicts with another instance of `Options`. + + if ( false === get_option( 'pll_language_from_content_available' ) ) { + update_option( + 'pll_language_from_content_available', + 0 === $options['force_lang'] ? 'yes' : 'no' + ); + } + + // Avoid 1 query on every pages if no wpml strings is registered + if ( ! get_option( 'polylang_wpml_strings' ) ) { + update_option( 'polylang_wpml_strings', array() ); + } + + // Don't use flush_rewrite_rules at network activation. See #32471 + // Thanks to RavanH for the trick. See https://polylang.wordpress.com/2015/06/10/polylang-1-7-6-and-multisite/ + // Rewrite rules are created at next page load :) + delete_option( 'rewrite_rules' ); + } + + /** + * Plugin deactivation + * + * @since 0.5 + * + * @return void + */ + protected function _deactivate() { + delete_option( 'rewrite_rules' ); // Don't use flush_rewrite_rules at network activation. See #32471 + } +} diff --git a/wp-content/plugins/polylang/install/plugin-updater.php b/wp-content/plugins/polylang/install/plugin-updater.php new file mode 100644 index 0000000..cbd708f --- /dev/null +++ b/wp-content/plugins/polylang/install/plugin-updater.php @@ -0,0 +1,729 @@ +api_url = trailingslashit( $_api_url ); + $this->api_data = $_api_data; + $this->plugin_file = $_plugin_file; + $this->name = plugin_basename( $_plugin_file ); + $this->slug = basename( dirname( $_plugin_file ) ); + $this->version = $_api_data['version']; + $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false; + $this->beta = ! empty( $this->api_data['beta'] ) ? true : false; + $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url ); + + $edd_plugin_data[ $this->slug ] = $this->api_data; + + /** + * Fires after the $edd_plugin_data is setup. + * + * @since x.x.x + * + * @param array $edd_plugin_data Array of EDD SL plugin data. + */ + do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data ); + + // Set up hooks. + $this->init(); + } + + /** + * Set up WordPress filters to hook into WP's update process. + * + * @uses add_filter() + * + * @return void + */ + public function init() { + + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) ); + add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 ); + add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 ); + add_action( 'admin_init', array( $this, 'show_changelog' ) ); + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update API just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @uses api_request() + * + * @param array $_transient_data Update array build by WordPress. + * @return array Modified update array with custom plugin data. + */ + public function check_update( $_transient_data ) { + + if ( ! is_object( $_transient_data ) ) { + $_transient_data = new stdClass(); + } + + if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) { + return $_transient_data; + } + + $current = $this->get_update_transient_data(); + if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) { + if ( version_compare( $this->version, $current->new_version, '<' ) ) { + $_transient_data->response[ $this->name ] = $current; + } else { + // Populating the no_update information is required to support auto-updates in WordPress 5.5. + $_transient_data->no_update[ $this->name ] = $current; + } + } + $_transient_data->last_checked = time(); + $_transient_data->checked[ $this->name ] = $this->version; + + return $_transient_data; + } + + /** + * Get repo API data from store. + * Save to cache. + * + * @return \stdClass + */ + public function get_repo_api_data() { + $version_info = $this->get_cached_version_info(); + + if ( false === $version_info ) { + $version_info = $this->api_request( + 'plugin_latest_version', + array( + 'slug' => $this->slug, + 'beta' => $this->beta, + ) + ); + if ( ! $version_info ) { + return false; + } + + // This is required for your plugin to support auto-updates in WordPress 5.5. + $version_info->plugin = $this->name; + $version_info->id = $this->name; + $version_info->tested = $this->get_tested_version( $version_info ); + if ( ! isset( $version_info->requires ) ) { + $version_info->requires = ''; + } + if ( ! isset( $version_info->requires_php ) ) { + $version_info->requires_php = ''; + } + + $this->set_version_info_cache( $version_info ); + } + + // Added by Polylang. + $defaults = array( + 'url' => '', + 'package' => '', + 'new_version' => '', + 'tested' => '', + 'requires' => '', + 'requires_php' => '', + 'icons' => new stdClass(), + 'banners' => new stdClass(), + ); + + $version_info = (object) array_merge( $defaults, (array) $version_info ); + // End of added by Polylang. + + return $version_info; + } + + /** + * Gets a limited set of data from the API response. + * This is used for the update_plugins transient. + * + * @since 3.8.12 + * @return \stdClass|false + */ + private function get_update_transient_data() { + $version_info = $this->get_repo_api_data(); + + if ( ! $version_info ) { + return false; + } + + $limited_data = new \stdClass(); + $limited_data->slug = $this->slug; + $limited_data->plugin = $this->name; + $limited_data->url = $version_info->url; + $limited_data->package = $version_info->package; + $limited_data->icons = $this->convert_object_to_array( $version_info->icons ); + $limited_data->banners = $this->convert_object_to_array( $version_info->banners ); + $limited_data->new_version = $version_info->new_version; + $limited_data->tested = $version_info->tested; + $limited_data->requires = $version_info->requires; + $limited_data->requires_php = $version_info->requires_php; + + return $limited_data; + } + + /** + * Gets the plugin's tested version. + * + * @since 1.9.2 + * @param object $version_info + * @return null|string + */ + private function get_tested_version( $version_info ) { + + // There is no tested version. + if ( empty( $version_info->tested ) ) { + return null; + } + + // Strip off extra version data so the result is x.y or x.y.z. + list( $current_wp_version ) = explode( '-', get_bloginfo( 'version' ) ); + + // The tested version is greater than or equal to the current WP version, no need to do anything. + if ( version_compare( $version_info->tested, $current_wp_version, '>=' ) ) { + return $version_info->tested; + } + $current_version_parts = explode( '.', $current_wp_version ); + $tested_parts = explode( '.', $version_info->tested ); + + // The current WordPress version is x.y.z, so update the tested version to match it. + if ( isset( $current_version_parts[2] ) && $current_version_parts[0] === $tested_parts[0] && $current_version_parts[1] === $tested_parts[1] ) { + $tested_parts[2] = $current_version_parts[2]; + } + + return implode( '.', $tested_parts ); + } + + /** + * Show the update notification on multisite subsites. + * + * @param string $file + * @param array $plugin + */ + public function show_update_notification( $file, $plugin ) { + + // Return early if in the network admin, or if this is not a multisite install. + if ( is_network_admin() || ! is_multisite() ) { + return; + } + + // Allow single site admins to see that an update is available. + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + if ( $this->name !== $file ) { + return; + } + + // Do not print any message if update does not exist. + $update_cache = get_site_transient( 'update_plugins' ); + + if ( ! isset( $update_cache->response[ $this->name ] ) ) { + if ( ! is_object( $update_cache ) ) { + $update_cache = new stdClass(); + } + $update_cache->response[ $this->name ] = $this->get_repo_api_data(); + } + + // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin. + if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) { + return; + } + + printf( + '', + $this->slug, + $file, + in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive' + ); + + echo ''; + echo '

    '; + + $changelog_link = ''; + if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) { + $changelog_link = add_query_arg( + array( + 'edd_sl_action' => 'view_plugin_changelog', + 'plugin' => urlencode( $this->name ), + 'slug' => urlencode( $this->slug ), + 'TB_iframe' => 'true', + 'width' => 77, + 'height' => 911, + ), + self_admin_url( 'index.php' ) + ); + } + $update_link = add_query_arg( + array( + 'action' => 'upgrade-plugin', + 'plugin' => urlencode( $this->name ), + ), + self_admin_url( 'update.php' ) + ); + + printf( + /* translators: the plugin name. */ + esc_html__( 'There is a new version of %1$s available.', 'polylang' ), + esc_html( $plugin['Name'] ) + ); + + if ( ! current_user_can( 'update_plugins' ) ) { + echo ' '; + esc_html_e( 'Contact your network administrator to install the update.', 'polylang' ); + } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */ + __( '%1$sView version %2$s details%3$s.', 'polylang' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '' + ); + } elseif ( ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. and 4. are opening anchor tags 2. the new plugin version 3. and 5. are closing anchor tags. */ + __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'polylang' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '', + '', + '' + ); + } else { + printf( + ' %1$s%2$s%3$s', + '', + esc_html__( 'Update now.', 'polylang' ), + '' + ); + } + + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin ); + + echo '

    '; + } + + /** + * Gets the plugins active in a multisite network. + * + * @return array + */ + private function get_active_plugins() { + $active_plugins = (array) get_option( 'active_plugins' ); + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' ); + + return array_merge( $active_plugins, array_keys( $active_network_plugins ) ); + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @uses api_request() + * + * @param mixed $_data + * @param string $_action + * @param object $_args + * @return object $_data + */ + public function plugins_api_filter( $_data, $_action = '', $_args = null ) { + + if ( 'plugin_information' !== $_action ) { + + return $_data; + + } + + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) { + + return $_data; + + } + + $to_send = array( + 'slug' => $this->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => array(), + 'reviews' => false, + 'icons' => array(), + ), + ); + + // Get the transient where we store the api request for this plugin for 24 hours + $edd_api_request_transient = $this->get_cached_version_info(); + + //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now. + if ( empty( $edd_api_request_transient ) ) { + + $api_response = $this->api_request( 'plugin_information', $to_send ); + if ( empty( $api_response ) ) { + return $_data; + } + + // Expires in 3 hours + $this->set_version_info_cache( $api_response ); + + $_data = $api_response; + } else { + $_data = $edd_api_request_transient; + } + + // Convert sections into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) { + $_data->sections = $this->convert_object_to_array( $_data->sections ); + } + + // Convert banners into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) { + $_data->banners = $this->convert_object_to_array( $_data->banners ); + } + + // Convert icons into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) { + $_data->icons = $this->convert_object_to_array( $_data->icons ); + } + + // Convert contributors into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) { + $_data->contributors = $this->convert_object_to_array( $_data->contributors ); + } + + if ( ! isset( $_data->plugin ) ) { + $_data->plugin = $this->name; + } + + if ( ! isset( $_data->version ) && ! empty( $_data->new_version ) ) { + $_data->version = $_data->new_version; + } + + return $_data; + } + + /** + * Convert some objects to arrays when injecting data into the update API + * + * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON + * decoding, they are objects. This method allows us to pass in the object and return an associative array. + * + * @since 3.6.5 + * + * @param stdClass $data + * + * @return array + */ + private function convert_object_to_array( $data ) { + if ( ! is_array( $data ) && ! is_object( $data ) ) { + return array(); + } + $new_data = array(); + foreach ( $data as $key => $value ) { + $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value; + } + + return $new_data; + } + + /** + * Disable SSL verification in order to prevent download update failures + * + * @param array $args + * @param string $url + * @return object $array + */ + public function http_request_args( $args, $url ) { + + if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) { + $args['sslverify'] = $this->verify_ssl(); + } + return $args; + } + + /** + * Calls the API and, if successfull, returns the object delivered by the API. + * + * @uses get_bloginfo() + * @uses wp_remote_post() + * @uses is_wp_error() + * + * @param string $_action The requested action. + * @param array $_data Parameters for the API action. + * @return false|object|void + */ + private function api_request( $_action, $_data ) { + $data = array_merge( $this->api_data, $_data ); + + if ( $data['slug'] !== $this->slug ) { + return; + } + + // Don't allow a plugin to ping itself + if ( trailingslashit( home_url() ) === $this->api_url ) { + return false; + } + + if ( $this->request_recently_failed() ) { + return false; + } + + return $this->get_version_from_remote(); + } + + /** + * Determines if a request has recently failed. + * + * @since 1.9.1 + * + * @return bool + */ + private function request_recently_failed() { + $failed_request_details = get_option( $this->failed_request_cache_key ); + + // Request has never failed. + if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) { + return false; + } + + /* + * Request previously failed, but the timeout has expired. + * This means we're allowed to try again. + */ + if ( time() > $failed_request_details ) { + delete_option( $this->failed_request_cache_key ); + + return false; + } + + return true; + } + + /** + * Logs a failed HTTP request for this API URL. + * We set a timestamp for 1 hour from now. This prevents future API requests from being + * made to this domain for 1 hour. Once the timestamp is in the past, API requests + * will be allowed again. This way if the site is down for some reason we don't bombard + * it with failed API requests. + * + * @see EDD_SL_Plugin_Updater::request_recently_failed + * + * @since 1.9.1 + */ + private function log_failed_request() { + update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) ); + } + + /** + * If available, show the changelog for sites in a multisite install. + */ + public function show_changelog() { + + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) { + return; + } + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) { + return; + } + + if ( ! current_user_can( 'update_plugins' ) ) { + wp_die( esc_html__( 'You do not have permission to install plugin updates', 'polylang' ), esc_html__( 'Error', 'polylang' ), array( 'response' => 403 ) ); + } + + $version_info = $this->get_repo_api_data(); + if ( isset( $version_info->sections ) ) { + $sections = $this->convert_object_to_array( $version_info->sections ); + if ( ! empty( $sections['changelog'] ) ) { + echo '
    ' . wp_kses_post( $sections['changelog'] ) . '
    '; + } + } + + exit; + } + + /** + * Gets the current version information from the remote site. + * + * @return array|false + */ + private function get_version_from_remote() { + $api_params = array( + 'edd_action' => 'get_version', + 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '', + 'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false, + 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false, + 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false, + 'slug' => $this->slug, + 'author' => $this->api_data['author'], + 'url' => home_url(), + 'beta' => $this->beta, + 'php_version' => phpversion(), + 'wp_version' => get_bloginfo( 'version' ), + ); + + /** + * Filters the parameters sent in the API request. + * + * @param array $api_params The array of data sent in the request. + * @param array $this->api_data The array of data set up in the class constructor. + * @param string $this->plugin_file The full path and filename of the file. + */ + $api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file ); + + $request = wp_remote_post( + $this->api_url, + array( + 'timeout' => 15, + 'sslverify' => $this->verify_ssl(), + 'body' => $api_params, + ) + ); + + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) { + $this->log_failed_request(); + + return false; + } + + $request = json_decode( wp_remote_retrieve_body( $request ) ); + + if ( $request && isset( $request->sections ) ) { + $request->sections = maybe_unserialize( $request->sections ); + } else { + $request = false; + } + + if ( $request && isset( $request->banners ) ) { + $request->banners = maybe_unserialize( $request->banners ); + } + + if ( $request && isset( $request->icons ) ) { + $request->icons = maybe_unserialize( $request->icons ); + } + + if ( ! empty( $request->sections ) ) { + foreach ( $request->sections as $key => $section ) { + $request->$key = (array) $section; + } + } + + return $request; + } + + /** + * Get the version info from the cache, if it exists. + * + * @param string $cache_key + * @return object + */ + public function get_cached_version_info( $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $cache = get_option( $cache_key ); + + // Cache is expired + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { + return false; + } + + // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point. + $cache['value'] = json_decode( $cache['value'] ); + if ( ! empty( $cache['value']->icons ) ) { + $cache['value']->icons = (array) $cache['value']->icons; + } + + return $cache['value']; + } + + /** + * Adds the plugin version information to the database. + * + * @param string $value + * @param string $cache_key + */ + public function set_version_info_cache( $value = '', $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $data = array( + 'timeout' => strtotime( '+3 hours', time() ), + 'value' => wp_json_encode( $value ), + ); + + update_option( $cache_key, $data, 'no' ); + + // Delete the duplicate option + delete_option( 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) ) ); + } + + /** + * Returns if the SSL of the store should be verified. + * + * @since 1.6.13 + * @return bool + */ + private function verify_ssl() { + return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this ); + } + + /** + * Gets the unique key (option name) for a plugin. + * + * @since 1.9.0 + * @return string + */ + private function get_cache_key() { + $string = $this->slug . $this->api_data['license'] . $this->beta; + + return 'edd_sl_' . md5( serialize( $string ) ); + } +} diff --git a/wp-content/plugins/polylang/install/t15s.php b/wp-content/plugins/polylang/install/t15s.php new file mode 100644 index 0000000..e24376c --- /dev/null +++ b/wp-content/plugins/polylang/install/t15s.php @@ -0,0 +1,237 @@ +slug = $slug; + $this->api_url = $api_url; + + add_action( 'init', array( self::class, 'register_clean_translations_cache' ), 9999 ); + add_filter( 'translations_api', array( $this, 'translations_api' ), 10, 3 ); + add_filter( 'site_transient_update_plugins', array( $this, 'site_transient_update_plugins' ) ); + } + + /** + * Short-circuits translations API requests for private projects. + * + * @since 2.6 + * + * @param bool|array $result The result object. Default false. + * @param string $requested_type The type of translations being requested. + * @param object $args Translation API arguments. + * @return bool|array + */ + public function translations_api( $result, $requested_type, $args ) { + if ( 'plugins' === $requested_type && $this->slug === $args['slug'] ) { + return self::get_translations( $args['slug'], $this->api_url ); + } + + return $result; + } + + /** + * Filters the translations transients to include the private plugin or theme. + * + * @see wp_get_translation_updates() + * + * @since 2.6 + * + * @param bool|array $value The transient value. + * @return bool|array + */ + public function site_transient_update_plugins( $value ) { + if ( ! $value ) { + $value = new stdClass(); + } + + if ( ! isset( $value->translations ) ) { + $value->translations = array(); + } + + $translations = self::get_translations( $this->slug, $this->api_url ); + + if ( ! isset( $translations['translations'] ) ) { + return $value; + } + + $installed_translations = self::get_installed_translations(); + + foreach ( (array) $translations['translations'] as $translation ) { + if ( in_array( $translation['language'], self::get_available_languages() ) ) { + if ( isset( $installed_translations[ $this->slug ][ $translation['language'] ] ) && $translation['updated'] ) { + $local = new DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] ); + $remote = new DateTime( $translation['updated'] ); + + if ( $local >= $remote ) { + continue; + } + } + + $translation['type'] = 'plugin'; + $translation['slug'] = $this->slug; + + $value->translations[] = $translation; + } + } + + return $value; + } + + /** + * Registers actions for clearing translation caches. + * + * @since 2.6 + * + * @return void + */ + public static function register_clean_translations_cache() { + add_action( 'set_site_transient_update_plugins', array( self::class, 'clean_translations_cache' ) ); + add_action( 'delete_site_transient_update_plugins', array( self::class, 'clean_translations_cache' ) ); + } + + /** + * Clears existing translation cache. + * + * @since 2.6 + * + * @return void + */ + public static function clean_translations_cache() { + $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN ); + + if ( ! is_object( $translations ) ) { + return; + } + + /* + * Don't delete the cache if the transient gets changed multiple times + * during a single request. Set cache lifetime to maximum 15 seconds. + */ + $cache_lifespan = 15; + $time_not_changed = isset( $translations->_last_checked ) && ( time() - $translations->_last_checked ) > $cache_lifespan; + + if ( ! $time_not_changed ) { + return; + } + + delete_site_transient( self::TRANSIENT_KEY_PLUGIN ); + } + + /** + * Gets the translations for a given project. + * + * @since 2.6 + * + * @param string $slug Project directory slug. + * @param string $url Full GlotPress API URL for the project. + * @return array Translation data. + */ + private static function get_translations( $slug, $url ) { + $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN ); + + if ( ! is_object( $translations ) ) { + $translations = new stdClass(); + } + + if ( isset( $translations->{$slug} ) && is_array( $translations->{$slug} ) ) { + return $translations->{$slug}; + } + + $result = json_decode( wp_remote_retrieve_body( wp_remote_get( $url, array( 'timeout' => 3 ) ) ), true ); + + // Nothing found. + if ( ! is_array( $result ) ) { + $result = array(); + } + + $translations->{$slug} = $result; + $translations->_last_checked = time(); + + set_site_transient( self::TRANSIENT_KEY_PLUGIN, $translations ); + return $result; + } + + /** + * Returns installed translations. + * + * Used to cache the result of wp_get_installed_translations() as it is very expensive. + * + * @since 2.8 + * + * @return array + */ + private static function get_installed_translations() { + if ( null === self::$installed_translations ) { + self::$installed_translations = wp_get_installed_translations( 'plugins' ); + } + return self::$installed_translations; + } + + /** + * Returns available languages. + * + * Used to cache the result of get_available_languages() as it is very expensive. + * + * @since 2.8 + * + * @return array + */ + private static function get_available_languages() { + if ( null === self::$available_languages ) { + self::$available_languages = get_available_languages(); + } + return self::$available_languages; + } +} diff --git a/wp-content/plugins/polylang/install/upgrade.php b/wp-content/plugins/polylang/install/upgrade.php new file mode 100644 index 0000000..bd60d32 --- /dev/null +++ b/wp-content/plugins/polylang/install/upgrade.php @@ -0,0 +1,362 @@ +options = &$options; + } + + /** + * Check if upgrade is possible otherwise die to avoid activation + * + * @since 1.2 + * + * @return void + */ + public function can_activate() { + if ( ! $this->can_upgrade() ) { + ob_start(); + $this->admin_notices(); // FIXME the error message is displayed two times + die( ob_get_contents() ); // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Upgrades if possible otherwise returns false to stop Polylang loading + * + * @since 1.2 + * + * @return bool true if upgrade is possible, false otherwise + */ + public function upgrade() { + if ( ! $this->can_upgrade() ) { + add_action( 'all_admin_notices', array( $this, 'admin_notices' ) ); + return false; + } + + delete_transient( 'pll_languages_list' ); + add_action( 'admin_init', array( $this, '_upgrade' ) ); + return true; + } + + + /** + * Check if we the previous version is not too old + * Upgrades if OK + * /!\ never start any upgrade before admin_init as it is likely to conflict with some other plugins + * + * @since 1.2 + * + * @return bool true if upgrade is possible, false otherwise + */ + public function can_upgrade() { + // Don't manage upgrade from version < 1.8 + return version_compare( $this->options['version'], '1.8', '>=' ); + } + + /** + * Displays a notice when ugrading from a too old version + * + * @since 1.0 + * + * @return void + */ + public function admin_notices() { + load_plugin_textdomain( 'polylang' ); + printf( + '

    %s

    %s

    ', + esc_html__( 'Polylang has been deactivated because you upgraded from a too old version.', 'polylang' ), + sprintf( + /* translators: %1$s and %2$s are Polylang version numbers */ + esc_html__( 'Before upgrading to %2$s, please upgrade to %1$s.', 'polylang' ), + '2.9', + POLYLANG_VERSION // phpcs:ignore WordPress.Security.EscapeOutput + ) + ); + } + + /** + * Upgrades the plugin depending on the previous version + * + * @since 1.2 + * + * @return void + */ + public function _upgrade() { + foreach ( array( '2.0.8', '2.1', '2.7', '3.4', '3.7' ) as $version ) { + if ( version_compare( $this->options['version'], $version, '<' ) ) { + $method_to_call = array( $this, 'upgrade_' . str_replace( '.', '_', $version ) ); + if ( is_callable( $method_to_call ) ) { + call_user_func( $method_to_call ); + } + } + } + + /** + * Fires after Polylang has been upgraded and before the new version is saved in options. + * + * @since 3.7 + */ + do_action( 'pll_upgrade' ); + + $this->options['previous_version'] = $this->options['version']; // Remember the previous version of Polylang since v1.7.7 + $this->options['version'] = POLYLANG_VERSION; + } + + /** + * Upgrades if the previous version is < 2.0.8 + * Changes the user meta 'user_lang' to 'locale' to match WP 4.7 choice + * + * @since 2.0.8 + * + * @return void + */ + protected function upgrade_2_0_8() { + global $wpdb; + $wpdb->update( $wpdb->usermeta, array( 'meta_key' => 'locale' ), array( 'meta_key' => 'user_lang' ) ); + } + + /** + * Upgrades if the previous version is < 2.1. + * Moves strings translations from polylang_mo post_content to post meta _pll_strings_translations. + * + * @since 2.1 + * + * @return void + */ + protected function upgrade_2_1() { + $posts = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + ) + ); + + if ( is_array( $posts ) ) { + foreach ( $posts as $post ) { + $meta = get_post_meta( $post->ID, '_pll_strings_translations', true ); + + if ( empty( $meta ) ) { + $strings = maybe_unserialize( $post->post_content ); + if ( is_array( $strings ) ) { + update_post_meta( $post->ID, '_pll_strings_translations', $strings ); + } + } + } + } + } + + /** + * Upgrades if the previous version is < 2.7 + * Replace numeric keys by hashes in WPML registered strings + * Dismiss the wizard notice for existing sites + * + * @since 2.7 + * + * @return void + */ + protected function upgrade_2_7() { + $strings = get_option( 'polylang_wpml_strings' ); + if ( is_array( $strings ) ) { + $new_strings = array(); + + foreach ( $strings as $string ) { + $context = $string['context']; + $name = $string['name']; + + $key = md5( "$context | $name" ); + $new_strings[ $key ] = $string; + } + update_option( 'polylang_wpml_strings', $new_strings ); + } + + PLL_Admin_Notices::dismiss( 'wizard' ); + } + + /** + * Upgrades if the previous version is < 3.4.0. + * + * @since 3.4 + * + * @return void + */ + protected function upgrade_3_4() { + $this->migrate_locale_fallback_to_language_description(); + + $this->migrate_strings_translations(); + } + + /** + * Upgrades if the previous version is < 3.7. + * Hides the "The language is set from content" option if it isn't the one selected. + * Cleans up strings translations so we don't store translations duplicated from the source. + * + * @since 3.7 + * + * @return void + */ + protected function upgrade_3_7() { + $this->allow_to_hide_language_from_content(); + $this->empty_duplicated_strings_translations(); + } + + /** + * Moves strings translations from post meta to term meta _pll_strings_translations. + * + * @since 3.4 + * + * @return void + */ + protected function migrate_strings_translations() { + $posts = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + ) + ); + + if ( ! is_array( $posts ) ) { + return; + } + + foreach ( $posts as $post ) { + $meta = get_post_meta( $post->ID, '_pll_strings_translations', true ); + + $term_id = (int) substr( $post->post_title, 12 ); + + // Do not delete post metas in case a user needs to rollback to Polylang < 3.4. + + if ( empty( $meta ) || ! is_array( $meta ) ) { + continue; + } + + update_term_meta( $term_id, '_pll_strings_translations', wp_slash( $meta ) ); + } + } + + /** + * Migrate locale fallback to language term description. + * + * @since 3.4 + * + * @return void + */ + protected function migrate_locale_fallback_to_language_description() { + // Migrate locale fallbacks from term metas to language term description. + $terms = get_terms( + array( + 'taxonomy' => 'language', + 'hide_empty' => false, + 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + array( + 'key' => 'fallback', + 'compare_key' => 'EXISTS', + ), + ), + ) + ); + + if ( ! is_array( $terms ) ) { + return; + } + + foreach ( $terms as $term ) { + $fallbacks = get_term_meta( $term->term_id, 'fallback', true ); + + delete_term_meta( $term->term_id, 'fallback' ); + + if ( empty( $fallbacks ) || ! is_array( $fallbacks ) ) { + // Empty or invalid value, should not happen. + continue; + } + + $description = maybe_unserialize( $term->description ); + $description = is_array( $description ) ? $description : array(); + + $description['fallbacks'] = $fallbacks; + /** @var string */ + $description = maybe_serialize( $description ); + + wp_update_term( $term->term_id, 'language', array( 'description' => $description ) ); + } + } + + /** + * Hides the language from content option if it is not the one selected. + * + * @since 3.7 + * + * @return void + */ + private function allow_to_hide_language_from_content() { + update_option( + 'pll_language_from_content_available', + 0 === $this->options['force_lang'] ? 'yes' : 'no' + ); + } + + /** + * Cleans up strings translations so we don't store translations duplicated from the source. + * + * @since 3.7 + * + * @return void + */ + private function empty_duplicated_strings_translations() { + $terms = get_terms( + array( + 'taxonomy' => 'language', + 'hide_empty' => false, + ) + ); + + if ( ! is_array( $terms ) ) { + return; + } + + foreach ( $terms as $term ) { + $strings = get_term_meta( $term->term_id, '_pll_strings_translations', true ); + + if ( empty( $strings ) || ! is_array( $strings ) ) { + continue; + } + + foreach ( $strings as $i => $tuple ) { + if ( $tuple[0] !== $tuple[1] ) { + continue; + } + + $strings[ $i ][1] = ''; + } + + update_term_meta( $term->term_id, '_pll_strings_translations', $strings ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php b/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php new file mode 100644 index 0000000..99f9307 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php @@ -0,0 +1,32 @@ + 'aq_resize' ) ) ); + } +} diff --git a/wp-content/plugins/polylang/integrations/aqua-resizer/load.php b/wp-content/plugins/polylang/integrations/aqua-resizer/load.php new file mode 100644 index 0000000..1325476 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/aqua-resizer/load.php @@ -0,0 +1,13 @@ +aq_resizer = new PLL_Aqua_Resizer(); +PLL_Integrations::instance()->aq_resizer->init(); diff --git a/wp-content/plugins/polylang/integrations/cache/cache-compat.php b/wp-content/plugins/polylang/integrations/cache/cache-compat.php new file mode 100644 index 0000000..9093fe5 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/cache/cache-compat.php @@ -0,0 +1,108 @@ +options['force_lang'] ) ? wp_parse_url( PLL()->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN; + $samesite = ( 3 === PLL()->options['force_lang'] ) ? 'None' : 'Lax'; + + /** This filter is documented in include/cookie.php */ + $expiration = (int) apply_filters( 'pll_cookie_expiration', YEAR_IN_SECONDS ); + + if ( 0 !== $expiration ) { + $format = 'var expirationDate = new Date(); + expirationDate.setTime( expirationDate.getTime() + %7$d * 1000 ); + document.cookie = "%1$s=%2$s; expires=" + expirationDate.toUTCString() + "; path=%3$s%4$s%5$s%6$s";'; + } else { + $format = 'document.cookie = "%1$s=%2$s; path=%3$s%4$s%5$s%6$s";'; + } + + $js = sprintf( + "(function() { + {$format} + }());\n", + esc_js( PLL_COOKIE ), + esc_js( pll_current_language() ), + esc_js( COOKIEPATH ), + $domain ? '; domain=' . esc_js( $domain ) : '', + is_ssl() ? '; secure' : '', + '; SameSite=' . $samesite, + esc_js( $expiration ) + ); + + // Need to register prior to enqueue empty script and add extra code to it. + wp_register_script( 'pll_cookie_script', '', array(), POLYLANG_VERSION, true ); + wp_enqueue_script( 'pll_cookie_script' ); + wp_add_inline_script( 'pll_cookie_script', $js ); + } + + /** + * Informs cache plugins not to cache the home in the default language + * When the detection of the browser preferred language is active + * + * @since 2.3 + */ + public function do_not_cache_site_home() { + if ( ! defined( 'DONOTCACHEPAGE' ) && PLL()->options['browser'] && PLL()->options['hide_default'] && is_front_page() && pll_current_language() === pll_default_language() ) { + define( 'DONOTCACHEPAGE', true ); + } + } + + /** + * Allows cache plugins to clean the right post type archive cache when cleaning a post cache. + * + * @since 3.0.5 + * + * @param int $post_id Post id. + */ + public function clean_post_cache( $post_id ) { + $lang = PLL()->model->post->get_language( $post_id ); + + if ( $lang ) { + $filter_callback = function ( $link, $post_type ) use ( $lang ) { + return pll_is_translated_post_type( $post_type ) && 'post' !== $post_type ? PLL()->links_model->switch_language_in_link( $link, $lang ) : $link; + }; + add_filter( 'post_type_archive_link', $filter_callback, 99, 2 ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/cache/load.php b/wp-content/plugins/polylang/integrations/cache/load.php new file mode 100644 index 0000000..3042f81 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/cache/load.php @@ -0,0 +1,20 @@ +cache_compat = new PLL_Cache_Compat(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/custom-field-template/cft.php b/wp-content/plugins/polylang/integrations/custom-field-template/cft.php new file mode 100644 index 0000000..1ce9a6b --- /dev/null +++ b/wp-content/plugins/polylang/integrations/custom-field-template/cft.php @@ -0,0 +1,35 @@ +ID; + } + } +} diff --git a/wp-content/plugins/polylang/integrations/custom-field-template/load.php b/wp-content/plugins/polylang/integrations/custom-field-template/load.php new file mode 100644 index 0000000..cb57809 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/custom-field-template/load.php @@ -0,0 +1,21 @@ +cft = new PLL_Cft(); + PLL_Integrations::instance()->cft->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php b/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php new file mode 100644 index 0000000..7acc2ca --- /dev/null +++ b/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php @@ -0,0 +1,81 @@ +options['force_lang'] > 1 ) { + // Don't redirect the main site + if ( is_main_site() ) { + return; + } + + // Don't redirect post previews + if ( isset( $_GET['preview'] ) && 'true' === $_GET['preview'] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + // Don't redirect theme customizer + if ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && 'on' === $_POST['customize'] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + // If we can't associate the requested domain to a language, redirect to the default domain + $requested_url = pll_get_requested_url(); + $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST ); + + $hosts = PLL()->links_model->get_hosts(); + $lang = array_search( $requested_host, $hosts ); + + if ( empty( $lang ) ) { + $status = get_site_option( 'dm_301_redirect' ) ? '301' : '302'; // Honor status redirect option + $redirect = str_replace( '://' . $requested_host, '://' . $hosts[ PLL()->options['default_lang'] ], $requested_url ); + wp_safe_redirect( $redirect, $status ); + exit; + } + } else { + // Otherwise rely on MU Domain Mapping + redirect_to_mapped_domain(); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/domain-mapping/load.php b/wp-content/plugins/polylang/integrations/domain-mapping/load.php new file mode 100644 index 0000000..ff6d610 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/domain-mapping/load.php @@ -0,0 +1,12 @@ +dm = new PLL_Domain_Mapping(); diff --git a/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php b/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php new file mode 100644 index 0000000..f738735 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php @@ -0,0 +1,37 @@ +duplicate_post = new PLL_Duplicate_Post(); + PLL_Integrations::instance()->duplicate_post->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/integrations.php b/wp-content/plugins/polylang/integrations/integrations.php new file mode 100644 index 0000000..f7224b2 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/integrations.php @@ -0,0 +1,60 @@ +init(); + } + + return self::$instance; + } + + /** + * Requires integrations. + * + * @since 3.7 + * + * @return void + */ + protected function init(): void { + // Loads external integrations. + foreach ( glob( __DIR__ . '/*/load.php', GLOB_NOSORT ) as $load_script ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + require_once $load_script; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable + } + } +} + +class_alias( 'PLL_Integrations', 'PLL_Plugins_Compat' ); // For Backward compatibility. diff --git a/wp-content/plugins/polylang/integrations/jetpack/featured-content.php b/wp-content/plugins/polylang/integrations/jetpack/featured-content.php new file mode 100644 index 0000000..9310a43 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/jetpack/featured-content.php @@ -0,0 +1,121 @@ +is_active() || false !== $featured_ids ) { + return $featured_ids; + } + + $settings = Featured_Content::get_setting(); + + if ( ! $term = get_term_by( 'name', $settings['tag-name'], 'post_tag' ) ) { + return $featured_ids; + } + + // Get featured tag translations + $tags = PLL()->model->term->get_translations( $term->term_id ); + $ids = array(); + + // Query for featured posts in all languages + // One query per language to get the correct number of posts per language + foreach ( $tags as $tag ) { + $args = array( + 'lang' => 0, // Avoid language filters. + 'fields' => 'ids', + 'numberposts' => Featured_Content::$max_posts, + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => (int) $tag, + ), + ), + ); + + // Available in Jetpack, but not in Twenty Fourteen. + if ( isset( Featured_Content::$post_types ) ) { + $args['post_type'] = Featured_Content::$post_types; + } + + $_ids = get_posts( $args ); + $ids = array_merge( $ids, $_ids ); + } + + $ids = array_map( 'absint', $ids ); + set_transient( 'featured_content_ids', $ids ); + + return $ids; + } + + /** + * Translates the featured tag id in featured content settings + * Mainly to allow hiding it when requested in featured content options + * Acts only on frontend + * + * @since 1.4 + * + * @param array $settings featured content settings + * @return array modified $settings + */ + public function option_featured_content( $settings ) { + if ( $this->is_active() && PLL() instanceof PLL_Frontend && $settings['tag-id'] && $tr = pll_get_term( $settings['tag-id'] ) ) { + $settings['tag-id'] = $tr; + } + + return $settings; + } +} diff --git a/wp-content/plugins/polylang/integrations/jetpack/jetpack.php b/wp-content/plugins/polylang/integrations/jetpack/jetpack.php new file mode 100644 index 0000000..a4f9897 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/jetpack/jetpack.php @@ -0,0 +1,137 @@ +options['force_lang'] > 1 ) { + add_filter( 'infinite_scroll_ajax_url', array( PLL()->links_model, 'site_url' ) ); + add_filter( 'infinite_scroll_js_settings', array( $this, 'jetpack_infinite_scroll_js_settings' ) ); + } + } + + /** + * Filter the Top Posts and Pages by language. + * Adapted from the same function in jetpack-3.0.2/3rd-party/wpml.php + * + * @since 1.5.4 + * + * @param array $posts Array of the most popular posts. + * @return array + */ + public function jetpack_widget_get_top_posts( $posts ) { + foreach ( $posts as $k => $post ) { + if ( pll_current_language() !== pll_get_post_language( $post['post_id'] ) ) { + unset( $posts[ $k ] ); + } + } + + return $posts; + } + + /** + * Filter the HTML of the Contact Form and output the one requested by language. + * Adapted from the same function in jetpack-3.0.2/3rd-party/wpml.php + * Keeps using 'icl_translate' as the function registers the string. + * + * @since 1.5.4 + * + * @param string $r Contact Form HTML output. + * @param string $field_label Field label. + * @return string + */ + public function grunion_contact_form_field_html_filter( $r, $field_label ) { + if ( function_exists( 'icl_translate' ) ) { + if ( pll_current_language() !== pll_default_language() ) { + $label_translation = icl_translate( 'jetpack ', $field_label . '_label', $field_label ); + $r = str_replace( $field_label, $label_translation, $r ); + } + } + + return $r; + } + + /** + * Adds opengraph support for locale and translations. + * + * @since 1.6 + * + * @param array $tags Opengraph tags to output. + * @return array + */ + public function jetpack_ogp( $tags ) { + if ( did_action( 'pll_init' ) ) { + foreach ( PLL()->model->get_languages_list() as $language ) { + if ( PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) { + $tags['og:locale:alternate'][] = $language->facebook; + } + if ( PLL()->curlang->slug === $language->slug && isset( $language->facebook ) ) { + $tags['og:locale'] = $language->facebook; + } + } + } + return $tags; + } + + /** + * Allows to make sure that related posts are in the correct language. + * + * @since 1.8 + * + * @param array $filters Array of ElasticSearch filters based on the post_id and args. + * @param string $post_id Post ID of the post for which we are retrieving Related Posts. + * @return array + */ + public function jetpack_relatedposts_filter_filters( $filters, $post_id ) { + $slug = sanitize_title( pll_get_post_language( $post_id, 'slug' ) ); + $filters[] = array( 'term' => array( 'taxonomy.language.slug' => $slug ) ); + return $filters; + } + + /** + * Fixes the settings history host for infinite scroll when using subdomains or multiple domains. + * + * @since 2.1 + * + * @param array $settings Infinite scroll JS settings outputted in the head. + * @return array + */ + public function jetpack_infinite_scroll_js_settings( $settings ) { + $settings['history']['host'] = wp_parse_url( pll_home_url(), PHP_URL_HOST ); // Jetpack uses get_option( 'home' ). + return $settings; + } +} diff --git a/wp-content/plugins/polylang/integrations/jetpack/load.php b/wp-content/plugins/polylang/integrations/jetpack/load.php new file mode 100644 index 0000000..9ed9766 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/jetpack/load.php @@ -0,0 +1,14 @@ +jetpack = new PLL_Jetpack(); // Must be loaded before the plugin is active. +add_action( 'pll_init', array( PLL_Integrations::instance()->featured_content = new PLL_Featured_Content(), 'init' ) ); diff --git a/wp-content/plugins/polylang/integrations/no-category-base/load.php b/wp-content/plugins/polylang/integrations/no-category-base/load.php new file mode 100644 index 0000000..6cc3722 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/no-category-base/load.php @@ -0,0 +1,13 @@ +no_category_base = new PLL_No_Category_Base(); +PLL_Integrations::instance()->no_category_base->init(); diff --git a/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php b/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php new file mode 100644 index 0000000..703ac6b --- /dev/null +++ b/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php @@ -0,0 +1,36 @@ +twenty_seventeen = new PLL_Twenty_Seventeen(), 'init' ) ); diff --git a/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php b/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php new file mode 100644 index 0000000..a75540f --- /dev/null +++ b/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php @@ -0,0 +1,30 @@ + 1 ), array( 'context' => 'Twenty Seventeen' ) ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-importer/load.php b/wp-content/plugins/polylang/integrations/wp-importer/load.php new file mode 100644 index 0000000..fca8ee3 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/load.php @@ -0,0 +1,12 @@ +wp_importer = new PLL_WordPress_Importer(); diff --git a/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php b/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php new file mode 100644 index 0000000..7d1e9f2 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php @@ -0,0 +1,70 @@ +getFileName() ) ) . '/languages' ); + + $GLOBALS['wp_import'] = new PLL_WP_Import(); + register_importer( 'wordpress', 'WordPress', __( 'Import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.', 'polylang' ), array( $GLOBALS['wp_import'], 'dispatch' ) ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText + } + + /** + * Sets the flag when importing a language and the file has been exported with Polylang < 1.8. + * + * @since 1.8 + * + * @param array $terms An array of arrays containing terms information form the WXR file. + * @return array + */ + public function wp_import_terms( $terms ) { + $languages = include POLYLANG_DIR . '/settings/languages.php'; + + foreach ( $terms as $key => $term ) { + if ( 'language' === $term['term_taxonomy'] ) { + $description = maybe_unserialize( $term['term_description'] ); + if ( empty( $description['flag_code'] ) && isset( $languages[ $description['locale'] ] ) ) { + $description['flag_code'] = $languages[ $description['locale'] ]['flag']; + $terms[ $key ]['term_description'] = maybe_serialize( $description ); + } + } + } + return $terms; + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php b/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php new file mode 100644 index 0000000..7d39f48 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php @@ -0,0 +1,213 @@ +terms. + foreach ( $this->terms as $term ) { + if ( 'post_translations' == $term['term_taxonomy'] ) { + $this->post_translations[] = $term; + } + if ( 'term_translations' == $term['term_taxonomy'] ) { + $term_translations[] = $term; + } + } + + parent::process_terms(); + + // First reset the core terms cache as WordPress Importer calls wp_suspend_cache_invalidation( true ); + wp_cache_set( 'last_changed', microtime(), 'terms' ); + + // Assign the default language in case the importer created the first language. + if ( empty( PLL()->options['default_lang'] ) ) { + $languages = get_terms( array( 'taxonomy' => 'language', 'hide_empty' => false, 'orderby' => 'term_id' ) ); + $default_lang = reset( $languages ); + PLL()->options['default_lang'] = $default_lang->slug; + } + + // Clean languages cache in case some of them were created during import. + PLL()->model->clean_languages_cache(); + + $this->remap_terms_relations( $term_translations ); + $this->remap_translations( $term_translations, $this->processed_terms ); + } + + /** + * Overrides WP_Import::process_post to remap posts translations + * Also merges strings translations from the WXR file to the existing ones + * + * @since 1.2 + */ + public function process_posts() { + $menu_items = $mo_posts = array(); + + // Store this for future usage as parent function unset $this->posts + foreach ( $this->posts as $post ) { + if ( 'nav_menu_item' == $post['post_type'] ) { + $menu_items[] = $post; + } + + if ( 0 === strpos( $post['post_title'], 'polylang_mo_' ) ) { + $mo_posts[] = $post; + } + } + + if ( ! empty( $mo_posts ) ) { + new PLL_MO(); // Just to register the polylang_mo post type before processing posts + } + + parent::process_posts(); + + PLL()->model->clean_languages_cache(); // To update the posts count in ( cached ) languages list + + $this->remap_translations( $this->post_translations, $this->processed_posts ); + unset( $this->post_translations ); + + // Language switcher menu items + foreach ( $menu_items as $item ) { + foreach ( $item['postmeta'] as $meta ) { + if ( '_pll_menu_item' == $meta['key'] ) { + update_post_meta( $this->processed_menu_items[ $item['post_id'] ], '_pll_menu_item', maybe_unserialize( $meta['value'] ) ); + } + } + } + + // Merge strings translations + foreach ( $mo_posts as $post ) { + $lang_id = (int) substr( $post['post_title'], 12 ); + + if ( ! empty( $this->processed_terms[ $lang_id ] ) ) { + if ( $strings = maybe_unserialize( $post['post_content'] ) ) { + $mo = new PLL_MO(); + $mo->import_from_db( $this->processed_terms[ $lang_id ] ); + foreach ( $strings as $msg ) { + $mo->add_entry_or_merge( $mo->make_entry( $msg[0], $msg[1] ) ); + } + $mo->export_to_db( $this->processed_terms[ $lang_id ] ); + } + } + // Delete the now useless imported post + wp_delete_post( $this->processed_posts[ $post['post_id'] ], true ); + } + } + + /** + * Remaps terms languages + * + * @since 1.2 + * + * @param array $terms array of terms in 'term_translations' taxonomy + */ + protected function remap_terms_relations( &$terms ) { + global $wpdb; + + $trs = array(); + + foreach ( $terms as $term ) { + $translations = maybe_unserialize( $term['term_description'] ); + foreach ( $translations as $slug => $old_id ) { + if ( $old_id && ! empty( $this->processed_terms[ $old_id ] ) && $lang = PLL()->model->get_language( $slug ) ) { + // Language relationship. + $trs[] = array( $this->processed_terms[ $old_id ], $lang->get_tax_prop( 'term_language', 'term_taxonomy_id' ) ); + + // Translation relationship. + $trs[] = array( $this->processed_terms[ $old_id ], get_term( $this->processed_terms[ $term['term_id'] ], 'term_translations' )->term_taxonomy_id ); + } + } + } + + // Insert term_relationships. + if ( ! empty( $trs ) ) { + // Make sure we don't attempt to insert already existing term relationships. + $existing_trs = $wpdb->get_results( + "SELECT tr.object_id, tr.term_taxonomy_id FROM {$wpdb->term_relationships} AS tr + INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id + WHERE tt.taxonomy IN ( 'term_language', 'term_translations' )" + ); + + foreach ( $existing_trs as $key => $tr ) { + $existing_trs[ $key ] = array( $tr->object_id, $tr->term_taxonomy_id ); + } + + $trs = array_diff( $trs, $existing_trs ); + + if ( ! empty( $trs ) ) { + $wpdb->query( + $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + sprintf( + "INSERT INTO {$wpdb->term_relationships} ( object_id, term_taxonomy_id ) VALUES %s", + implode( ',', array_fill( 0, count( $trs ), '( %d, %d )' ) ) + ), + array_merge( ...$trs ) + ) + ); + } + } + } + + /** + * Remaps translations for both posts and terms + * + * @since 1.2 + * + * @param array $terms array of terms in 'post_translations' or 'term_translations' taxonomies + * @param array $processed_objects array of posts or terms processed by WordPress Importer + */ + protected function remap_translations( &$terms, &$processed_objects ) { + global $wpdb; + + $u = array(); + + foreach ( $terms as $term ) { + $translations = maybe_unserialize( $term['term_description'] ); + $new_translations = array(); + + foreach ( $translations as $slug => $old_id ) { + if ( $old_id && ! empty( $processed_objects[ $old_id ] ) ) { + $new_translations[ $slug ] = $processed_objects[ $old_id ]; + } + } + + if ( ! empty( $new_translations ) ) { + $u['case'][] = array( $this->processed_terms[ $term['term_id'] ], maybe_serialize( $new_translations ) ); + $u['in'][] = (int) $this->processed_terms[ $term['term_id'] ]; + } + } + + if ( ! empty( $u ) ) { + $wpdb->query( + $wpdb->prepare( + sprintf( + "UPDATE {$wpdb->term_taxonomy} + SET description = ( CASE term_id %s END ) + WHERE term_id IN (%s)", + implode( ' ', array_fill( 0, count( $u['case'] ), 'WHEN %d THEN %s' ) ), + implode( ',', array_fill( 0, count( $u['in'] ), '%d' ) ) + ), + array_merge( array_merge( ...$u['case'] ), $u['in'] ) + ) + ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php b/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php new file mode 100644 index 0000000..33d5518 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php @@ -0,0 +1,72 @@ +is_media_translated[ $post_id ] = ( count( pll_get_post_translations( $post_id ) ) > 1 ); + } + + /** + * Deletes the WP Offload Media information from the attachment being deleted. + * That way WP Offload Media won't delete the file stored in the cloud. + * Done after Polylang has deleted the translations information, to avoid the synchronization of the deletion + * and of course before WP Offload Media deletes the file, normally at priority 20. + * + * @since 2.6 + * + * @param int $post_id Id of the attachment being deleted. + */ + public function prevent_file_deletion( $post_id ) { + if ( ! empty( $this->is_media_translated[ $post_id ] ) ) { + delete_post_meta( $post_id, 'amazonS3_info' ); + delete_post_meta( $post_id, 'as3cf_filesize_total' ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-offload-media/load.php b/wp-content/plugins/polylang/integrations/wp-offload-media/load.php new file mode 100644 index 0000000..15acfd0 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-offload-media/load.php @@ -0,0 +1,20 @@ +as3cf = new PLL_AS3CF(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wp-sweep/load.php b/wp-content/plugins/polylang/integrations/wp-sweep/load.php new file mode 100644 index 0000000..0ce0e17 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-sweep/load.php @@ -0,0 +1,21 @@ +wp_sweep = new PLL_WP_Sweep(); + PLL_Integrations::instance()->wp_sweep->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php b/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php new file mode 100644 index 0000000..b9a195d --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php @@ -0,0 +1,62 @@ +model->get_languages_list() as $language ) { + $excluded_term_ids = array_merge( + $excluded_term_ids, + array_values( $language->get_tax_props( 'term_id' ) ) + ); + } + + return array_unique( $excluded_term_ids ); + } +} diff --git a/wp-content/plugins/polylang/integrations/wpseo/load.php b/wp-content/plugins/polylang/integrations/wpseo/load.php new file mode 100644 index 0000000..3965a23 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/load.php @@ -0,0 +1,20 @@ +wpseo = new PLL_WPSEO(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php b/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php new file mode 100644 index 0000000..4b6e701 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php @@ -0,0 +1,54 @@ +locale = $locale; + } + + /** + * Returns the meta Opengraph alternate locale meta tag + * + * @since 2.7.3 + * + * @return string + */ + public function present() { + return sprintf( '', esc_attr( $this->get() ) ); + } + + /** + * Returns the alternate locale + * + * @since 2.7.3 + * + * @return string + */ + public function get() { + return $this->locale; + } +} diff --git a/wp-content/plugins/polylang/integrations/wpseo/wpseo.php b/wp-content/plugins/polylang/integrations/wpseo/wpseo.php new file mode 100644 index 0000000..934a4fe --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/wpseo.php @@ -0,0 +1,517 @@ +options['force_lang'] > 1 ) { + add_filter( 'wpseo_enable_xml_sitemap_transient_caching', '__return_false' ); // Disable cache! otherwise WPSEO keeps only one domain (thanks to Junaid Bhura) + add_filter( 'home_url', array( $this, 'wpseo_home_url' ), 10, 2 ); // Fix home_url + add_action( 'setup_theme', array( $this, 'maybe_deactivate_sitemap' ) ); // Deactivate sitemaps for inactive languages. + } else { + // Get all terms in all languages when the language is set from the content or directory name + add_filter( 'get_terms_args', array( $this, 'wpseo_remove_terms_filter' ) ); + add_action( 'pre_get_posts', array( $this, 'before_sitemap' ), 0 ); // Needs to be fired before WPSEO_Sitemaps::redirect() + } + + add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) ); + add_filter( 'wpseo_frontend_presenters', array( $this, 'wpseo_frontend_presenters' ) ); + add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) ); + add_filter( 'wpseo_frontend_presentation', array( $this, 'frontend_presentation' ) ); + add_filter( 'wpseo_breadcrumb_indexables', array( $this, 'breadcrumb_indexables' ) ); + } else { + add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 4 ); + add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 ); + add_filter( 'pll_post_metas_to_export', array( $this, 'export_post_metas' ) ); + + // Yoast SEO adds the columns hooks only for the 'inline-save' action. We need them for 'pll_update_post_rows' too. + if ( wp_doing_ajax() && isset( $_POST['action'] ) && 'pll_update_post_rows' === $_POST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $GLOBALS['wpseo_meta_columns'] = new WPSEO_Meta_Columns(); + } + } + } + + /** + * Registers options for translation. + * + * @since 2.9 + */ + public function wpseo_translate_options() { + if ( method_exists( 'WPSEO_Options', 'clear_cache' ) ) { + WPSEO_Options::clear_cache(); + } + + $keys = array( + 'title-*', + 'metadesc-*', + 'bctitle-*', + 'breadcrumbs-sep', + 'breadcrumbs-home', + 'breadcrumbs-prefix', + 'breadcrumbs-archiveprefix', + 'breadcrumbs-searchprefix', + 'breadcrumbs-404crumb', + 'company_name', + 'rssbefore', + 'rssafter', + 'social-title-*', + 'social-description-*', + ); + + new PLL_Translate_Option( 'wpseo_titles', array_fill_keys( $keys, 1 ), array( 'context' => 'wordpress-seo' ) ); + + $keys = array( + 'og_frontpage_title', + 'og_frontpage_desc', + ); + + new PLL_Translate_Option( 'wpseo_social', array_fill_keys( $keys, 1 ), array( 'context' => 'wordpress-seo' ) ); + } + + /** + * Fixes the home url as well as the stylesheet url, + * only when using multiple domains or subdomains. + * + * @since 1.6.4 + * + * @param string $url The complete URL including scheme and path. + * @param string $path Path relative to the home URL. + * @return $url + */ + public function wpseo_home_url( $url, $path ) { + if ( empty( $path ) ) { + $path = ltrim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' ); + } + + if ( preg_match( '#sitemap(_index)?\.xml|([^\/]+?)-?sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) { + $url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang ); + } + + return $url; + } + + /** + * Get active languages for the sitemaps + * + * @since 2.0 + * + * @return array list of active language slugs, empty if all languages are active + */ + protected function wpseo_get_active_languages() { + $languages = PLL()->model->get_languages_list(); + if ( wp_list_filter( $languages, array( 'active' => false ) ) ) { + return wp_list_pluck( wp_list_filter( $languages, array( 'active' => false ), 'NOT' ), 'slug' ); + } + return array(); + } + + /** + * Modifies the sql request for posts sitemaps. + * + * @since 1.6.4 + * + * @param string $sql JOIN clause. + * @param string $post_type Post type. + * @return string + */ + public function wpseo_posts_join( $sql, $post_type ) { + return pll_is_translated_post_type( $post_type ) ? $sql . PLL()->model->post->join_clause() : $sql; + } + + /** + * Modifies the sql request for posts sitemaps. + * + * @since 1.6.4 + * + * @param string $sql WHERE clause. + * @param string $post_type Post type. + * @return string + */ + public function wpseo_posts_where( $sql, $post_type ) { + if ( ! pll_is_translated_post_type( $post_type ) ) { + return $sql; + } + + if ( PLL()->options['force_lang'] > 1 && PLL()->curlang instanceof PLL_Language ) { + return $sql . PLL()->model->post->where_clause( PLL()->curlang ); + } + + $languages = $this->wpseo_get_active_languages(); + + if ( empty( $languages ) ) { // Empty when all languages are active. + $languages = pll_languages_list(); + } + + return $sql . PLL()->model->post->where_clause( $languages ); + } + + /** + * Removes the language filter (and remove inactive languages) for the taxonomy sitemaps + * Only when the language is set from the content or directory name + * + * @since 1.0.3 + * + * @param array $args get_terms arguments + * @return array modified list of arguments + */ + public function wpseo_remove_terms_filter( $args ) { + if ( isset( $GLOBALS['wp_query']->query['sitemap'] ) ) { + $args['lang'] = implode( ',', $this->wpseo_get_active_languages() ); + } + return $args; + } + + /** + * Deactivates the sitemap for inactive languages when using subdomains or multiple domains + * + * @since 2.6.1 + */ + public function maybe_deactivate_sitemap() { + global $wpseo_sitemaps; + + if ( isset( $wpseo_sitemaps ) ) { + $active_languages = $this->wpseo_get_active_languages(); + if ( ! empty( $active_languages ) && ! in_array( pll_current_language(), $active_languages ) ) { + remove_action( 'pre_get_posts', array( $wpseo_sitemaps, 'redirect' ), 1 ); + } + } + } + + /** + * Add filters before the sitemap is evaluated and outputted. + * + * @since 2.6 + * + * @param WP_Query $query Instance of WP_Query being filtered. + */ + public function before_sitemap( $query ) { + $type = $query->get( 'sitemap' ); + + // Add the post post type archives in all languages to the sitemap + // Add the homepages for all languages to the sitemap when the front page displays posts + if ( $type && pll_is_translated_post_type( $type ) && ( 'post' !== $type || ! get_option( 'page_on_front' ) ) ) { + add_filter( "wpseo_sitemap_{$type}_content", array( $this, 'add_post_type_archive' ) ); + } + } + + /** + * Generates a post type archive sitemap url + * + * @since 2.6.1 + * + * @param string $link The url. + * @param string $post_type The post type name. + * @return string Formatted sitemap url. + */ + protected function format_sitemap_url( $link, $post_type ) { + global $wpseo_sitemaps; + + return $wpseo_sitemaps->renderer->sitemap_url( + array( + 'loc' => $link, + 'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ), + 'pri' => 1, + 'chf' => 'daily', + ) + ); + } + + /** + * Adds the home and post type archives urls for all (active) languages to the sitemap + * + * @since 2.6 + * + * @param string $str additional urls to sitemap post + * @return string + */ + public function add_post_type_archive( $str ) { + $post_type = substr( substr( current_filter(), 14 ), 0, -8 ); + $post_type_obj = get_post_type_object( $post_type ); + $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'active' => false ), 'NOT' ); + + if ( 'post' === $post_type ) { + if ( ! empty( PLL()->options['hide_default'] ) ) { + // The home url is of course already added by WPSEO. + $languages = wp_list_filter( $languages, array( 'slug' => pll_default_language() ), 'NOT' ); + } + + foreach ( $languages as $lang ) { + $str .= $this->format_sitemap_url( pll_home_url( $lang->slug ), $post_type ); + } + } elseif ( $post_type_obj->has_archive ) { + // Exclude cases where a post type archive is attached to a page (ex: WooCommerce). + $slug = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive; + + if ( ! wpcom_vip_get_page_by_path( $slug ) ) { + // The post type archive in the current language is already added by WPSEO. + $languages = wp_list_filter( $languages, array( 'slug' => pll_current_language() ), 'NOT' ); + + foreach ( $languages as $lang ) { + PLL()->curlang = $lang; // Switch the language to get the correct archive link. + $link = get_post_type_archive_link( $post_type ); + $str .= $this->format_sitemap_url( $link, $post_type ); + } + } + } + + return $str; + } + + /** + * Filters the home url. + * + * @since 1.1.2 + * + * @param array $arr The list of files or functions for which `home_url()` must be filtered. + * @return array + */ + public function wpseo_home_url_white_list( $arr ) { + return array_merge( $arr, array( array( 'file' => 'wordpress-seo' ) ) ); + } + + /** + * Get alternate language codes for Opengraph. + * + * @since 2.7.3 + * + * @return string[] + */ + protected function get_ogp_alternate_languages() { + $alternates = array(); + + foreach ( PLL()->model->get_languages_list() as $language ) { + if ( isset( PLL()->curlang ) && PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) { + $alternates[] = $language->facebook; + } + } + + // There is a risk that 2 languages have the same Facebook locale. So let's make sure to output each locale only once. + return array_unique( $alternates ); + } + + /** + * Adds opengraph support for translations + * + * @since 2.7.3 + * + * @param array $presenters An array of objects implementing Abstract_Indexable_Presenter + * @return array + */ + public function wpseo_frontend_presenters( $presenters ) { + $_presenters = array(); + + foreach ( $presenters as $presenter ) { + $_presenters[] = $presenter; + if ( $presenter instanceof Yoast\WP\SEO\Presenters\Open_Graph\Locale_Presenter ) { + foreach ( $this->get_ogp_alternate_languages() as $lang ) { + $_presenters[] = new PLL_WPSEO_OGP( $lang ); + } + } + } + return $_presenters; + } + + /** + * Fixes the canonical front page url as unlike WP, WPSEO does not add a trailing slash to the canonical front page url. + * + * @since 1.7.10 + * + * @param string $url The canonical URL evaluated by Yoast SEO. + * @return $url + */ + public function wpseo_canonical( $url ) { + return is_front_page( $url ) && get_option( 'permalink_structure' ) ? trailingslashit( $url ) : $url; + } + + /** + * Fixes the links and strings stored in the indexable table since Yoast SEO 14.0 + * + * @since 2.8.2 + * + * @param object $presentation The indexable presentation. + * @return object + */ + public function frontend_presentation( $presentation ) { + switch ( $presentation->model->object_type ) { + case 'home-page': + $presentation->model->title = WPSEO_Options::get( 'title-home-wpseo' ); + $presentation->model->description = WPSEO_Options::get( 'metadesc-home-wpseo' ); + $presentation->model->open_graph_title = WPSEO_Options::get( 'og_frontpage_title' ); + $presentation->model->open_graph_description = WPSEO_Options::get( 'og_frontpage_desc' ); + break; + + case 'post-type-archive': + if ( pll_is_translated_post_type( $presentation->model->object_sub_type ) ) { + $presentation->model->title = WPSEO_Options::get( 'title-ptarchive-' . $presentation->model->object_sub_type ); + $presentation->model->description = WPSEO_Options::get( 'metadesc-ptarchive-' . $presentation->model->object_sub_type ); + } + break; + + case 'system-page': + switch ( $presentation->model->object_sub_type ) { + case '404': + $presentation->model->title = WPSEO_Options::get( 'title-404-wpseo' ); + break; + case 'search-result': + $presentation->model->title = WPSEO_Options::get( 'title-search-wpseo' ); + break; + } + break; + } + + return $presentation; + } + + /** + * Fixes the breadcrumb links and strings stored in the indexable table since Yoast SEO 14.0. + * + * In version 17.0, the breadcrumb links do not honor the filter `wpseo_dynamic_permalinks_enabled`. + * + * @since 2.8.3 + * + * @param array $indexables An array of Indexable objects. + * @return array + */ + public function breadcrumb_indexables( $indexables ) { + foreach ( $indexables as &$indexable ) { + if ( 'home-page' === $indexable->object_type || ( 'post' === $indexable->object_type && 'page' === $indexable->object_sub_type && get_option( 'page_on_front' ) === $indexable->object_id ) ) { + // Handles both when the front page displays the list of posts or a static page. + $indexable->permalink = pll_home_url(); + $indexable->breadcrumb_title = pll__( WPSEO_Options::get( 'breadcrumbs-home' ) ); + } elseif ( 'post' === $indexable->object_type && 'page' === $indexable->object_sub_type && get_option( 'page_for_posts' ) === $indexable->object_id ) { + $indexable->permalink = get_permalink( $indexable->object_id ); + } elseif ( 'post-type-archive' === $indexable->object_type && pll_is_translated_post_type( $indexable->object_sub_type ) ) { + $indexable->permalink = get_post_type_archive_link( $indexable->object_sub_type ); + $breadcrumb_title = WPSEO_Options::get( 'bctitle-ptarchive-' . $indexable->object_sub_type ); + $breadcrumb_title = $breadcrumb_title ?: $indexable->breadcrumb_title; // The option may be empty. + $indexable->breadcrumb_title = pll__( $breadcrumb_title ); + } elseif ( 'term' === $indexable->object_type && pll_is_translated_taxonomy( $indexable->object_sub_type ) ) { + $indexable->permalink = get_term_link( $indexable->object_id ); + } + } + + return $indexables; + } + + /** + * Copies or synchronizes the metas. + * + * @since 2.3.3 + * + * @param string[] $keys List of custom fields names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @return array + */ + public function copy_post_metas( $keys, $sync, $from, $to ) { + if ( ! $sync ) { + // Text requiring translation. + $keys = array_merge( $keys, $this->get_translatable_meta_keys() ); + + // Copy the image urls. + $keys[] = '_yoast_wpseo_opengraph-image'; + $keys[] = '_yoast_wpseo_twitter-image'; + + $keys[] = '_yoast_wpseo_meta-robots-noindex'; + $keys[] = '_yoast_wpseo_meta-robots-nofollow'; + $keys[] = '_yoast_wpseo_meta-robots-adv'; + } + + $taxonomies = get_taxonomies( + array( + 'hierarchical' => true, + 'public' => true, + ) + ); + + $sync_taxonomies = PLL()->sync->taxonomies->get_taxonomies_to_copy( $sync, $from, $to ); + + $taxonomies = array_intersect( $taxonomies, $sync_taxonomies ); + + foreach ( $taxonomies as $taxonomy ) { + $keys[] = '_yoast_wpseo_primary_' . $taxonomy; + } + + return $keys; + } + + /** + * Translate the primary term during the synchronization process + * + * @since 2.3.3 + * + * @param int $value Meta value. + * @param string $key Meta key. + * @param string $lang Language of target. + * @return int + */ + public function translate_post_meta( $value, $key, $lang ) { + if ( 0 !== strpos( $key, '_yoast_wpseo_primary_' ) ) { + return $value; + } + + $taxonomy = str_replace( '_yoast_wpseo_primary_', '', $key ); + if ( ! PLL()->model->is_translated_taxonomy( $taxonomy ) ) { + return $value; + } + + return pll_get_term( $value, $lang ); + } + + /** + * Adds the yoast translatable metas to export. + * + * @param array $metas An array of post metas (keyed with meta keys) to export. + * @return array The modified array of post metas to export. + */ + public function export_post_metas( $metas ) { + $metas_to_export = array_fill_keys( $this->get_translatable_meta_keys(), 1 ); + + return array_merge( $metas, $metas_to_export ); + } + + /** + * Returns the meta keys with translatable text. + * + * @since 3.3 + * + * @return string[] + */ + protected function get_translatable_meta_keys() { + return array( + '_yoast_wpseo_title', + '_yoast_wpseo_metadesc', + '_yoast_wpseo_bctitle', + '_yoast_wpseo_focuskw', + '_yoast_wpseo_opengraph-title', + '_yoast_wpseo_opengraph-description', + '_yoast_wpseo_twitter-title', + '_yoast_wpseo_twitter-description', + ); + } +} diff --git a/wp-content/plugins/polylang/integrations/yarpp/load.php b/wp-content/plugins/polylang/integrations/yarpp/load.php new file mode 100644 index 0000000..68d5484 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/yarpp/load.php @@ -0,0 +1,20 @@ +yarpp = new PLL_Yarpp(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/yarpp/yarpp.php b/wp-content/plugins/polylang/integrations/yarpp/yarpp.php new file mode 100644 index 0000000..11e66d6 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/yarpp/yarpp.php @@ -0,0 +1,20 @@ +yarpp_support = 1; + } +} diff --git a/wp-content/plugins/polylang/js/build/admin.js b/wp-content/plugins/polylang/js/build/admin.js new file mode 100644 index 0000000..98052e3 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/admin.js @@ -0,0 +1,58 @@ +/******/ "use strict"; + +;// ./js/src/lib/ajax-filter/index.js +/** + * @package Polylang + */ + +/** + * Adds data to all ajax requests made with jQuery. + * + * @since 3.7 + * + * @param {Object} data The data to add. + * @returns {void} + */ +function ajaxFilter( data ) { + if ( 'undefined' === typeof jQuery || ! data ) { + return; + } + + const dataStr = jQuery.param( data ); + + jQuery.ajaxPrefilter( function ( options ) { + if ( -1 === options.url.indexOf( ajaxurl ) && -1 === ajaxurl.indexOf( options.url ) ) { + return; + } + + if ( + 'undefined' === typeof options.data || + null === options.data || + 'string' === typeof options.data && '' === options.data.trim() + ) { + // An empty string or null/undefined. + options.data = dataStr; + } else if ( 'string' === typeof options.data ) { + // A non-empty string: can be a JSON string or a query string. + try { + options.data = JSON.stringify( Object.assign( JSON.parse( options.data ), data ) ); + } catch ( exception ) { + // A non-empty non-JSON string is considered a query string. + options.data = `${ options.data }&${ dataStr }`; + } + } else if ( jQuery.isPlainObject( options.data ) ) { + // An object. + options.data = Object.assign( options.data, data ); + } + } ); +} + +;// ./js/src/admin.js +/** + * @package Polylang + */ + + + +ajaxFilter( pll_admin?.ajax_filter ); + diff --git a/wp-content/plugins/polylang/js/build/admin.min.js b/wp-content/plugins/polylang/js/build/admin.min.js new file mode 100644 index 0000000..e86b42b --- /dev/null +++ b/wp-content/plugins/polylang/js/build/admin.min.js @@ -0,0 +1 @@ +"use strict";function ajaxFilter(a){if("undefined"==typeof jQuery||!a)return;const t=jQuery.param(a);jQuery.ajaxPrefilter(function(e){if(-1!==e.url.indexOf(ajaxurl)||-1!==ajaxurl.indexOf(e.url))if(void 0===e.data||null===e.data||"string"==typeof e.data&&""===e.data.trim())e.data=t;else if("string"==typeof e.data)try{e.data=JSON.stringify(Object.assign(JSON.parse(e.data),a))}catch(a){e.data=`${e.data}&${t}`}else jQuery.isPlainObject(e.data)&&(e.data=Object.assign(e.data,a))})}ajaxFilter(pll_admin?.ajax_filter); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/block-editor.js b/wp-content/plugins/polylang/js/build/block-editor.js new file mode 100644 index 0000000..ef87ebf --- /dev/null +++ b/wp-content/plugins/polylang/js/build/block-editor.js @@ -0,0 +1,405 @@ +/******/ "use strict"; + +;// ./js/src/lib/confirmation-modal.js +/** + * @package Polylang + */ + +const languagesList = jQuery( '.post_lang_choice' ); + +// Dialog box for alerting the user about a risky changing. +const initializeConfirmationModal = () => { + // We can't use underscore or lodash in this common code because it depends of the context classic or block editor. + // Classic editor underscore is loaded, Block editor lodash is loaded. + const { __ } = wp.i18n; + + // Create dialog container. + const dialogContainer = jQuery( + '
    ', + { + id: 'pll-dialog', + style: 'display:none;' + } + ).text( __( 'Are you sure you want to change the language of the current content?', 'polylang' ) ); + + // Put it after languages list dropdown. + // PHPCS ignore dialogContainer is a new safe HTML code generated above. + languagesList.after( dialogContainer ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + + const dialogResult = new Promise( + ( confirm, cancel ) => { + const confirmDialog = ( what ) => { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + switch ( what ) { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + case 'yes': + // Confirm the new language. + languagesList.data( 'old-value', languagesList.children( ':selected' ).first().val() ); + confirm(); + break; + case 'no': + // Revert to the old language. + languagesList.val( languagesList.data( 'old-value' ) ); + cancel( 'Cancel' ); + break; + } + dialogContainer.dialog( 'close' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + } // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + + // Initialize dialog box in the case a language is selected but not added in the list. + const dialogOptions = { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: __( 'Change language', 'polylang' ), + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( jQuery( 'body' ).hasClass( 'rtl' ) ) { + jQuery( this ).parent().css( + { + right: jQuery( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + }, + close: function ( event, ui ) { + // When we're closing the dialog box we need to cancel the language change as we click on Cancel button. + confirmDialog( 'no' ); + }, + buttons: [ + { + text: __( 'OK', 'polylang' ), + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: __( 'Cancel', 'polylang' ), + click: function ( event ) { + confirmDialog( 'no' ); + } + } + ] + }; + + if ( jQuery.ui.version >= '1.12.0' ) { + Object.assign( dialogOptions, { classes: { 'ui-dialog': 'pll-confirmation-modal' } } ); + } else { + Object.assign( dialogOptions, { dialogClass: 'pll-confirmation-modal' } ); // jQuery UI 1.11.4 - WP < 5.6 + } + + dialogContainer.dialog( dialogOptions ); + } + ); + return { dialogContainer, dialogResult }; +} + +const initializeLanguageOldValue = () => { + // Keep the old language value to be able to compare to the new one and revert to it if necessary. + languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() ); +}; + +;// ./js/src/lib/metabox-autocomplete.js +/** + * @package Polylang + */ + +// Translations autocomplete input box. +function initMetaboxAutoComplete() { + jQuery('.tr_lang').each( + function () { + var tr_lang = jQuery(this).attr('id').substring(8); + var td = jQuery(this).parent().parent().siblings('.pll-edit-column'); + + jQuery(this).autocomplete( + { + minLength: 0, + source: ajaxurl + '?action=pll_posts_not_translated' + + '&post_language=' + jQuery('.post_lang_choice').val() + + '&translation_language=' + tr_lang + + '&post_type=' + jQuery('#post_type').val() + + '&_pll_nonce=' + jQuery('#_pll_nonce').val(), + select: function (event, ui) { + jQuery('#htr_lang_' + tr_lang).val(ui.item.id); + // ui.item.link is built and come from server side and is well escaped when necessary + td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + }, + } + ); + + // when the input box is emptied + jQuery(this).on( + 'blur', + function () { + if ( ! jQuery(this).val() ) { + jQuery('#htr_lang_' + tr_lang).val(0); + // Value is retrieved from HTML already generated server side + td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + } + ); + } + ); +} + +;// ./js/src/lib/filter-path-middleware.js +/** + * @package Polylang + */ + +/** + * Filters requests for translatable entities. + * This logic is shared across all Polylang plugins. + * + * @since 3.5 + * + * @param {APIFetchOptions} options + * @param {Array} filteredRoutes + * @param {CallableFunction} filter + * @returns {APIFetchOptions} + */ +const filterPathMiddleware = ( options, filteredRoutes, filter ) => { + const cleanPath = options.path.split( '?' )[0].replace(/^\/+|\/+$/g, ''); // Get path without query parameters and trim '/'. + + return Object.values( filteredRoutes ).find( ( path ) => cleanPath === path ) ? filter( options ) : options; +} + +/* harmony default export */ const filter_path_middleware = (filterPathMiddleware); + +;// ./js/src/block-editor.js +/** + * @package Polylang + */ + + + + + + + +/** + * Filter REST API requests to add the language in the request + * + * @since 2.5 + */ +wp.apiFetch.use( + function ( options, next ) { + /* + * If options.url is defined, this is not a REST request but a direct call to post.php for legacy metaboxes. + * If `filteredRoutes` is not defined, return early. + */ + if ( 'undefined' !== typeof options.url || 'undefined' === typeof pllFilteredRoutes ) { + return next( options ); + } + + return next( filter_path_middleware( options, pllFilteredRoutes, addLanguageParameter ) ); + } +); + +/** + * Gets the language of the currently edited post, fallback to default language if none is found. + * + * @since 2.5 + * + * @return {Element.value} + */ +function getCurrentLanguage() { + const lang = document.querySelector( '[name=post_lang_choice]' ); + + if ( null === lang ) { + return pllDefaultLanguage; + } + + return lang.value; +} + +/** + * Adds language parameter according to the current one (query string for GET, body for PUT and POST). + * + * @since 3.5 + * + * @param {APIFetchOptions} options + * @returns {APIFetchOptions} + */ +function addLanguageParameter( options ) { + if ( 'undefined' === typeof options.data || null === options.data ) { + // GET + options.path += ( ( options.path.indexOf( '?' ) >= 0 ) ? '&lang=' : '?lang=' ) + getCurrentLanguage(); + } else { + // PUT, POST + options.data.lang = getCurrentLanguage(); + } + + return options; +} + +/** + * Handles internals of the metabox: + * Language select, autocomplete input field. + * + * @since 1.5 + * + * Save post after lang choice is done and redirect to the same page for refreshing all the data. + * + * @since 2.5 + * + * Link post saving after refreshing the metabox. + * + * @since 3.0 + */ +jQuery( + function ( $ ) { + // Initialize current language to be able to compare if it changes. + initializeLanguageOldValue(); + + + // Ajax for changing the post's language in the languages metabox + $( '.post_lang_choice' ).on( + 'change', + function ( event ) { + const { select, dispatch, subscribe } = wp.data; + const emptyPost = isEmptyPost(); + const { addQueryArgs } = wp.url; + + // Initialize the confirmation dialog box. + const confirmationModal = initializeConfirmationModal(); + const { dialogContainer : dialog } = confirmationModal; + let { dialogResult } = confirmationModal; + const selectedOption = event.target; // The selected option in the dropdown list. + + // Specific case for empty posts. + // Place at the beginning because window.location change triggers automatically page reloading. + if ( location.pathname.match( /post-new.php/gi ) && emptyPost ) { + reloadPageForEmptyPost( selectedOption.value ); + } + + // Otherwise send an ajax request to refresh the legacy metabox and set the post language with the new language. + // It needs a confirmation of the user before changing the language. + // Need to wait the ajax response before triggering the block editor post save action. + if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! emptyPost ) { + dialog.dialog( 'open' ); + } else { + // Update the old language with the new one to be able to compare it in the next change. + // Because the page isn't reloaded in this case. + initializeLanguageOldValue(); + dialogResult = Promise.resolve(); + } + + dialogResult.then( + () => { + let data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + action: 'post_lang_choice', + lang: selectedOption.value, + post_type: $( '#post_type' ).val(), + post_id: $( '#post_ID' ).val(), + _pll_nonce: $( '#_pll_nonce' ).val() + } + + // Update post language in database as soon as possible. + // Because, in addition of the block editor save process, the legacy metabox uses a post.php process to update the language and is too late compared to the page reload. + $.post( + ajaxurl, + data, + function () { + blockEditorSavePostAndReloadPage(); + } + ); + }, + () => {} // Do nothing when promise is rejected by clicking the Cancel dialog button. + ); + + function isEmptyPost() { + const editor = select( 'core/editor' ); + + return ! editor.getEditedPostAttribute( 'title' )?.trim() && ! editor.getEditedPostContent() && ! editor.getEditedPostAttribute( 'excerpt' )?.trim(); + } + + /** + * Reload the block editor page for empty posts. + * + * @param {string} lang The target language code. + */ + function reloadPageForEmptyPost( lang ) { + // Change the new_lang parameter with the new language value for reloading the page + // WPCS location.search is never written in the page, just used to reload page with the right value of new_lang + // new_lang input is controlled server side in PHP. The value come from the dropdown list of language returned and escaped server side. + // Notice that window.location changing triggers automatically page reloading. + if ( -1 != location.search.indexOf( 'new_lang' ) ) { + // use regexp non capturing group to replace new_lang parameter no matter where it is and capture other parameters which can be behind it + window.location.search = window.location.search.replace( /(?:new_lang=[^&]*)(&)?(.*)/, 'new_lang=' + lang + '$1$2' ); // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment + } else { + window.location.search = window.location.search + ( ( -1 != window.location.search.indexOf( '?' ) ) ? '&' : '?' ) + 'new_lang=' + lang; // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment + } + }; + + /** + * Triggers block editor post save and reload the block editor page when everything is ok. + */ + function blockEditorSavePostAndReloadPage() { + + let unsubscribe = null; + const previousPost = select( 'core/editor').getCurrentPost(); + + // Listen if the savePost is completely done by subscribing to its events. + const savePostIsDone = new Promise( + function ( resolve, reject ) { + unsubscribe = subscribe( + function () { + const post = select( 'core/editor').getCurrentPost(); + const { id, status, type } = post; + const error = select( 'core' ) + .getLastEntitySaveError( + 'postType', + type, + id + ); + + if ( error ) { + reject(); + } + + if ( previousPost.modified !== post.modified ) { + + if ( location.pathname.match( /post-new.php/gi ) && status !== 'auto-draft' && id ) { + window.history.replaceState( + { id }, + 'Post ' + id, + addQueryArgs( 'post.php', { post: id, action: 'edit' } ) + ); + } + resolve(); + } + } + ); + } + ); + + // Triggers the post save. + dispatch( 'core/editor' ).savePost(); + + // Process + savePostIsDone.then( + function () { + // If the post is well saved, we can reload the page + window.location.reload(); + }, + function () { + // If the post save failed + unsubscribe(); + } + ).catch( + function () { + // If an exception is thrown + unsubscribe(); + } + ); + }; + } + ); + + initMetaboxAutoComplete(); + } +); + diff --git a/wp-content/plugins/polylang/js/build/block-editor.min.js b/wp-content/plugins/polylang/js/build/block-editor.min.js new file mode 100644 index 0000000..2961405 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/block-editor.min.js @@ -0,0 +1 @@ +"use strict";const languagesList=jQuery(".post_lang_choice"),initializeConfirmationModal=()=>{const{__:t}=wp.i18n,e=jQuery("
    ",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(e);const a=new Promise((a,n)=>{const i=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),a();break;case"no":languagesList.val(languagesList.data("old-value")),n("Cancel")}e.dialog("close")},l={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,e){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,e){i("no")},buttons:[{text:t("OK","polylang"),click:function(t){i("yes")}},{text:t("Cancel","polylang"),click:function(t){i("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(l,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(l,{dialogClass:"pll-confirmation-modal"}),e.dialog(l)});return{dialogContainer:e,dialogResult:a}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each(function(){var t=jQuery(this).attr("id").substring(8),e=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(a,n){jQuery("#htr_lang_"+t).val(n.item.id),e.html(n.item.link)}}),jQuery(this).on("blur",function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),e.html(e.siblings(".hidden").children().clone()))})})}const filterPathMiddleware=(t,e,a)=>{const n=t.path.split("?")[0].replace(/^\/+|\/+$/g,"");return Object.values(e).find(t=>n===t)?a(t):t},filter_path_middleware=filterPathMiddleware;function getCurrentLanguage(){const t=document.querySelector("[name=post_lang_choice]");return null===t?pllDefaultLanguage:t.value}function addLanguageParameter(t){return void 0===t.data||null===t.data?t.path+=(t.path.indexOf("?")>=0?"&lang=":"?lang=")+getCurrentLanguage():t.data.lang=getCurrentLanguage(),t}wp.apiFetch.use(function(t,e){return void 0!==t.url||"undefined"==typeof pllFilteredRoutes?e(t):e(filter_path_middleware(t,pllFilteredRoutes,addLanguageParameter))}),jQuery(function(t){initializeLanguageOldValue(),t(".post_lang_choice").on("change",function(e){const{select:a,dispatch:n,subscribe:i}=wp.data,l=function(){const t=a("core/editor");return!t.getEditedPostAttribute("title")?.trim()&&!t.getEditedPostContent()&&!t.getEditedPostAttribute("excerpt")?.trim()}(),{addQueryArgs:o}=wp.url,r=initializeConfirmationModal(),{dialogContainer:s}=r;let{dialogResult:u}=r;const c=e.target;var d;location.pathname.match(/post-new.php/gi)&&l&&(d=c.value,-1!=location.search.indexOf("new_lang")?window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,"new_lang="+d+"$1$2"):window.location.search=window.location.search+(-1!=window.location.search.indexOf("?")?"&":"?")+"new_lang="+d),t(this).data("old-value")===c.value||l?(initializeLanguageOldValue(),u=Promise.resolve()):s.dialog("open"),u.then(()=>{let e={action:"post_lang_choice",lang:c.value,post_type:t("#post_type").val(),post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,function(){!function(){let t=null;const e=a("core/editor").getCurrentPost(),l=new Promise(function(n,l){t=i(function(){const t=a("core/editor").getCurrentPost(),{id:i,status:r,type:s}=t;a("core").getLastEntitySaveError("postType",s,i)&&l(),e.modified!==t.modified&&(location.pathname.match(/post-new.php/gi)&&"auto-draft"!==r&&i&&window.history.replaceState({id:i},"Post "+i,o("post.php",{post:i,action:"edit"})),n())})});n("core/editor").savePost(),l.then(function(){window.location.reload()},function(){t()}).catch(function(){t()})}()})},()=>{})}),initMetaboxAutoComplete()}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/classic-editor.js b/wp-content/plugins/polylang/js/build/classic-editor.js new file mode 100644 index 0000000..d8c266b --- /dev/null +++ b/wp-content/plugins/polylang/js/build/classic-editor.js @@ -0,0 +1,466 @@ +/******/ "use strict"; + +;// ./js/src/lib/confirmation-modal.js +/** + * @package Polylang + */ + +const languagesList = jQuery( '.post_lang_choice' ); + +// Dialog box for alerting the user about a risky changing. +const initializeConfirmationModal = () => { + // We can't use underscore or lodash in this common code because it depends of the context classic or block editor. + // Classic editor underscore is loaded, Block editor lodash is loaded. + const { __ } = wp.i18n; + + // Create dialog container. + const dialogContainer = jQuery( + '
    ', + { + id: 'pll-dialog', + style: 'display:none;' + } + ).text( __( 'Are you sure you want to change the language of the current content?', 'polylang' ) ); + + // Put it after languages list dropdown. + // PHPCS ignore dialogContainer is a new safe HTML code generated above. + languagesList.after( dialogContainer ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + + const dialogResult = new Promise( + ( confirm, cancel ) => { + const confirmDialog = ( what ) => { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + switch ( what ) { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + case 'yes': + // Confirm the new language. + languagesList.data( 'old-value', languagesList.children( ':selected' ).first().val() ); + confirm(); + break; + case 'no': + // Revert to the old language. + languagesList.val( languagesList.data( 'old-value' ) ); + cancel( 'Cancel' ); + break; + } + dialogContainer.dialog( 'close' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + } // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + + // Initialize dialog box in the case a language is selected but not added in the list. + const dialogOptions = { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: __( 'Change language', 'polylang' ), + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( jQuery( 'body' ).hasClass( 'rtl' ) ) { + jQuery( this ).parent().css( + { + right: jQuery( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + }, + close: function ( event, ui ) { + // When we're closing the dialog box we need to cancel the language change as we click on Cancel button. + confirmDialog( 'no' ); + }, + buttons: [ + { + text: __( 'OK', 'polylang' ), + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: __( 'Cancel', 'polylang' ), + click: function ( event ) { + confirmDialog( 'no' ); + } + } + ] + }; + + if ( jQuery.ui.version >= '1.12.0' ) { + Object.assign( dialogOptions, { classes: { 'ui-dialog': 'pll-confirmation-modal' } } ); + } else { + Object.assign( dialogOptions, { dialogClass: 'pll-confirmation-modal' } ); // jQuery UI 1.11.4 - WP < 5.6 + } + + dialogContainer.dialog( dialogOptions ); + } + ); + return { dialogContainer, dialogResult }; +} + +const initializeLanguageOldValue = () => { + // Keep the old language value to be able to compare to the new one and revert to it if necessary. + languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() ); +}; + +;// ./js/src/lib/metabox-autocomplete.js +/** + * @package Polylang + */ + +// Translations autocomplete input box. +function initMetaboxAutoComplete() { + jQuery('.tr_lang').each( + function () { + var tr_lang = jQuery(this).attr('id').substring(8); + var td = jQuery(this).parent().parent().siblings('.pll-edit-column'); + + jQuery(this).autocomplete( + { + minLength: 0, + source: ajaxurl + '?action=pll_posts_not_translated' + + '&post_language=' + jQuery('.post_lang_choice').val() + + '&translation_language=' + tr_lang + + '&post_type=' + jQuery('#post_type').val() + + '&_pll_nonce=' + jQuery('#_pll_nonce').val(), + select: function (event, ui) { + jQuery('#htr_lang_' + tr_lang).val(ui.item.id); + // ui.item.link is built and come from server side and is well escaped when necessary + td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + }, + } + ); + + // when the input box is emptied + jQuery(this).on( + 'blur', + function () { + if ( ! jQuery(this).val() ) { + jQuery('#htr_lang_' + tr_lang).val(0); + // Value is retrieved from HTML already generated server side + td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + } + ); + } + ); +} + +;// ./js/src/classic-editor.js +/** + * @package Polylang + */ + + + + + +// tag suggest in metabox +jQuery( + function ( $ ) { + $.ajaxPrefilter( + function ( options, originalOptions, jqXHR ) { + var lang = $( '.post_lang_choice' ).val(); + if ( 'string' === typeof options.data && -1 !== options.url.indexOf( 'action=ajax-tag-search' ) && lang ) { + options.data = 'lang=' + lang + '&' + options.data; + } + } + ); + } +); + +// overrides tagBox.get +jQuery( + function ( $ ) { + // overrides function to add the language + tagBox.get = function ( id ) { + var tax = id.substr( id.indexOf( '-' ) + 1 ); + + // add the language in the $_POST variable + var data = { + action: 'get-tagcloud', + lang: $( '.post_lang_choice' ).val(), + tax: tax + } + + $.post( + ajaxurl, + data, + function ( r, stat ) { + if ( 0 == r || 'success' != stat ) { + r = wpAjax.broken; + } + + // @see code from WordPress core https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/js/tags-box.js#L291 + // @see wp_generate_tag_cloud function which generate the escaped HTML https://github.com/WordPress/WordPress/blob/a02b5cc2a8eecb8e076fbb7cf4de7bd2ec8a8eb1/wp-includes/category-template.php#L966-L975 + r = $( '
    ' ).addClass( 'the-tagcloud' ).attr( 'id', 'tagcloud-' + tax ).html( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + $( 'a', r ).on( + 'click', + function () { + tagBox.flushTags( $( this ).closest( '.inside' ).children( '.tagsdiv' ), this ); + return false; + } + ); + + var tagCloud = $( '#tagcloud-' + tax ); + // add an if else condition to allow modifying the tags outputted when switching the language + var v = tagCloud.css( 'display' ); + if ( v ) { + // See the comment above when r variable is created. + $( '#tagcloud-' + tax ).replaceWith( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + $( '#tagcloud-' + tax ).css( 'display', v ); + } + else { + // See the comment above when r variable is created. + $( '#' + id ).after( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + } + } + ); + } + } +); + +jQuery( + function ( $ ) { + // collect taxonomies - code partly copied from WordPress + var taxonomies = new Array(); + $( '.categorydiv' ).each( + function () { + var this_id = $( this ).attr( 'id' ), taxonomyParts, taxonomy; + + taxonomyParts = this_id.split( '-' ); + taxonomyParts.shift(); + taxonomy = taxonomyParts.join( '-' ); + taxonomies.push( taxonomy ); // store the taxonomy for future use + + // add our hidden field in the new category form - for each hierarchical taxonomy + // to set the language when creating a new category + // html code inserted come from html code itself. + $( '#' + taxonomy + '-add-submit' ).before( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.before + $( '' ).attr( 'type', 'hidden' ) + .attr( 'id', taxonomy + '-lang' ) + .attr( 'name', 'term_lang_choice' ) + .attr( 'value', $( '.post_lang_choice' ).val() ) + ); + } + ); + + // Initialize current language to be able to compare if it changes. + initializeLanguageOldValue(); + + // ajax for changing the post's language in the languages metabox + $( '.post_lang_choice' ).on( + 'change', + function ( event ) { + // Initialize the confirmation dialog box. + const confirmationModal = initializeConfirmationModal(); + const { dialogContainer: dialog } = confirmationModal; + let { dialogResult } = confirmationModal; + // The selected option in the dropdown list. + const selectedOption = event.target; + + if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! isEmptyPost() ) { + dialog.dialog( 'open' ); + } else { + dialogResult = Promise.resolve(); + } + + dialogResult.then( + () => { + var data = { + action: 'post_lang_choice', + lang: selectedOption.value, + post_type: $( '#post_type' ).val(), + taxonomies: taxonomies, + post_id: $( '#post_ID' ).val(), + _pll_nonce: $( '#_pll_nonce' ).val() + } + + $.post( + ajaxurl, + data, + function ( response ) { + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + switch ( this.what ) { + case 'translations': // translations fields + // Data is built and come from server side and is well escaped when necessary + $( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + initMetaboxAutoComplete(); + break; + case 'taxonomy': // categories metabox for posts + var tax = this.data; + // @see wp_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L175 + // @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/class-walker-category-checklist.php#L89-L111 + $( '#' + tax + 'checklist' ).html( this.supplemental.all ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // @see wp_popular_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L236 + $( '#' + tax + 'checklist-pop' ).html( this.supplemental.populars ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // @see wp_dropdown_categories https://github.com/WordPress/WordPress/blob/5.5.1/wp-includes/category-template.php#L336 + // which is called by PLL_Admin_Classic_Editor::post_lang_choice to generate supplemental.dropdown + $( '#new' + tax + '_parent' ).replaceWith( this.supplemental.dropdown ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + $( '#' + tax + '-lang' ).val( $( '.post_lang_choice' ).val() ); // hidden field + break; + case 'pages': // parent dropdown list for pages + // @see wp_dropdown_pages https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/post-template.php#L1186-L1208 + // @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/class-walker-page-dropdown.php#L88 + $( '#parent_id' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + break; + case 'flag': // flag in front of the select dropdown + // Data is built and come from server side and is well escaped when necessary + $( '.pll-select-flag' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + break; + case 'permalink': // Sample permalink + var div = $( '#edit-slug-box' ); + if ( '-1' != this.data && div.children().length ) { + // @see get_sample_permalink_html https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/post.php#L1425-L1454 + div.html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + break; + } + } + ); + + // Creates an event once the language has been successfully changed. + const onPostLangChoice = new CustomEvent( + "onPostLangChoice", + { + detail: { + lang: JSON.parse( selectedOption.options[selectedOption.options.selectedIndex].getAttribute( 'data-lang' ) ) + }, + } + ); + document.dispatchEvent( onPostLangChoice ); + } + ) + }, + () => {} // Do nothing when promise is rejected by clicking the Cancel dialog button. + ); + + function isEmptyPost() { + const title = $( 'input#title' ).val(); + const content = $( 'textarea#content' ).val(); + const excerpt = $( 'textarea#excerpt' ).val(); + + return ! title && ! content && ! excerpt; + } + } + ); + + // Listen to `onPostLangChoice` to perform actions after the language has been changed. + document.addEventListener( + 'onPostLangChoice', + ( e ) => { + // Update the old language with the new one to be able to compare it in the next changing. + initializeLanguageOldValue(); + + // Modifies the language in the tag cloud. + $( '.tagcloud-link' ).each( + function () { + var id = $( this ).attr( 'id' ); + tagBox.get( id ); + } + ); + + // Modifies the text direction. + let dir = e.detail.lang.is_rtl ? 'rtl' : 'ltr' + $( 'body' ).removeClass( 'pll-dir-rtl' ).removeClass( 'pll-dir-ltr' ).addClass( 'pll-dir-' + dir ); + $( '#content_ifr' ).contents().find( 'html' ).attr( 'lang', e.detail.lang.locale ).attr( 'dir', dir ); + $( '#content_ifr' ).contents().find( 'body' ).attr( 'dir', dir ); + + // Refresh media libraries. + pll.media.resetAllAttachmentsCollections(); + } + ); + + initMetaboxAutoComplete(); + } +); + +/** + * @since 3.0 + * + * @namespace pll + */ +var pll = window.pll || {}; + +/** + * @since 3.0 + * + * @namespace pll.media + */ +_.extend( pll, { media: {} } ); + +/** + * @since 3.0 + * + * @alias pll.media + * @memberOf pll + * @namespace + */ +var media = _.extend( + pll.media, /** @lends pll.media.prototype */ + { + /** + * TODO: Find a way to delete references to Attachments collections that are not used anywhere else. + * + * @type {wp.media.model.Attachments} + */ + attachmentsCollections : [], + + /** + * Imitates { @see wp.media.query } but log all Attachments collections created. + * + * @param {Object} [props] + * @return {wp.media.model.Attachments} + */ + query: function ( props ) { + var attachments = pll.media.query.delegate( props ); + + pll.media.attachmentsCollections.push( attachments ); + + return attachments; + }, + + resetAllAttachmentsCollections: function () { + this.attachmentsCollections.forEach( + function ( attachmentsCollection ) { + /** + * First reset the { @see wp.media.model.Attachments } collection. + * Then, if it is mirroring a { @see wp.media.model.Query } collection, + * refresh this one too, so it will fetch new data from the server, + * and then the wp.media.model.Attachments collection will synchronize with the new data. + */ + attachmentsCollection.reset(); + if (attachmentsCollection.mirroring) { + attachmentsCollection.mirroring._hasMore = true; + attachmentsCollection.mirroring.reset(); + } + } + ); + } + } +); + +if ( 'undefined' !== typeof wp && 'undefined' !== typeof wp.media ) { + + /** + * @since 3.0 + * + * @memberOf pll.media + */ + media.query = _.extend( + media.query, /** @lends pll.media.query prototype */ + { + /** + * @type Function References WordPress { @see wp.media.query } constructor + */ + delegate: wp.media.query + } + ) + + // Substitute WordPress media query shortcut with our decorated function. + wp.media.query = media.query + +} + diff --git a/wp-content/plugins/polylang/js/build/classic-editor.min.js b/wp-content/plugins/polylang/js/build/classic-editor.min.js new file mode 100644 index 0000000..dbd7c04 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/classic-editor.min.js @@ -0,0 +1 @@ +"use strict";const languagesList=jQuery(".post_lang_choice"),initializeConfirmationModal=()=>{const{__:t}=wp.i18n,a=jQuery("
    ",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(a);const e=new Promise((e,l)=>{const n=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),e();break;case"no":languagesList.val(languagesList.data("old-value")),l("Cancel")}a.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,a){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,a){n("no")},buttons:[{text:t("OK","polylang"),click:function(t){n("yes")}},{text:t("Cancel","polylang"),click:function(t){n("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),a.dialog(i)});return{dialogContainer:a,dialogResult:e}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each(function(){var t=jQuery(this).attr("id").substring(8),a=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(e,l){jQuery("#htr_lang_"+t).val(l.item.id),a.html(l.item.link)}}),jQuery(this).on("blur",function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),a.html(a.siblings(".hidden").children().clone()))})})}jQuery(function(t){t.ajaxPrefilter(function(a,e,l){var n=t(".post_lang_choice").val();"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&n&&(a.data="lang="+n+"&"+a.data)})}),jQuery(function(t){tagBox.get=function(a){var e=a.substr(a.indexOf("-")+1),l={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:e};t.post(ajaxurl,l,function(l,n){0!=l&&"success"==n||(l=wpAjax.broken),l=t("
    ").addClass("the-tagcloud").attr("id","tagcloud-"+e).html(l),t("a",l).on("click",function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1});var i=t("#tagcloud-"+e).css("display");i?(t("#tagcloud-"+e).replaceWith(l),t("#tagcloud-"+e).css("display",i)):t("#"+a).after(l)})}}),jQuery(function(t){var a=new Array;t(".categorydiv").each(function(){var e,l;(e=t(this).attr("id").split("-")).shift(),l=e.join("-"),a.push(l),t("#"+l+"-add-submit").before(t("").attr("type","hidden").attr("id",l+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))}),initializeLanguageOldValue(),t(".post_lang_choice").on("change",function(e){const l=initializeConfirmationModal(),{dialogContainer:n}=l;let{dialogResult:i}=l;const o=e.target;t(this).data("old-value")===o.value||function(){const a=t("input#title").val(),e=t("textarea#content").val(),l=t("textarea#excerpt").val();return!a&&!e&&!l}()?i=Promise.resolve():n.dialog("open"),i.then(()=>{var e={action:"post_lang_choice",lang:o.value,post_type:t("#post_type").val(),taxonomies:a,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,function(a){var e=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(e.responses,function(){switch(this.what){case"translations":t(".translations").html(this.data),initMetaboxAutoComplete();break;case"taxonomy":var a=this.data;t("#"+a+"checklist").html(this.supplemental.all),t("#"+a+"checklist-pop").html(this.supplemental.populars),t("#new"+a+"_parent").replaceWith(this.supplemental.dropdown),t("#"+a+"-lang").val(t(".post_lang_choice").val());break;case"pages":t("#parent_id").html(this.data);break;case"flag":t(".pll-select-flag").html(this.data);break;case"permalink":var e=t("#edit-slug-box");"-1"!=this.data&&e.children().length&&e.html(this.data)}});const l=new CustomEvent("onPostLangChoice",{detail:{lang:JSON.parse(o.options[o.options.selectedIndex].getAttribute("data-lang"))}});document.dispatchEvent(l)})},()=>{})}),document.addEventListener("onPostLangChoice",a=>{initializeLanguageOldValue(),t(".tagcloud-link").each(function(){var a=t(this).attr("id");tagBox.get(a)});let e=a.detail.lang.is_rtl?"rtl":"ltr";t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+e),t("#content_ifr").contents().find("html").attr("lang",a.detail.lang.locale).attr("dir",e),t("#content_ifr").contents().find("body").attr("dir",e),pll.media.resetAllAttachmentsCollections()}),initMetaboxAutoComplete()});var pll=window.pll||{};_.extend(pll,{media:{}});var media=_.extend(pll.media,{attachmentsCollections:[],query:function(t){var a=pll.media.query.delegate(t);return pll.media.attachmentsCollections.push(a),a},resetAllAttachmentsCollections:function(){this.attachmentsCollections.forEach(function(t){t.reset(),t.mirroring&&(t.mirroring._hasMore=!0,t.mirroring.reset())})}});"undefined"!=typeof wp&&void 0!==wp.media&&(media.query=_.extend(media.query,{delegate:wp.media.query}),wp.media.query=media.query); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/languages-step.js b/wp-content/plugins/polylang/js/build/languages-step.js new file mode 100644 index 0000000..8077fa9 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/languages-step.js @@ -0,0 +1,303 @@ +/** + * @package Polylang + */ + +jQuery( + function ( $ ) { + var addLanguageForm = $( '.languages-step' ); // Form element. + var languageFields = $( '#language-fields' ); // Element where to append hidden fields for creating language. + var languagesTable = $( '#languages' ); // Table element contains languages list to create. + var languagesListTable = $( '#languages tbody' ); // Table rows with languages list to create. + var definedLanguagesListTable = $( '#defined-languages tbody' ); // Table rows with already defined languages list. + var languagesList = $( '#lang_list' ); // Select form element with predefined languages without already created languages. + var nextStepButton = $( '[name="save_step"]' ); // The button for continuing to the next step. + var messagesContainer = $( '#messages' ); // Element where to display error messages. + var languagesMap = new Map(); // Languages map object for managing the languages to create. + var dialog = $( '#dialog' ); // Dialog box for alerting the language selected has not been added to the list. + + /** + * Add a language in the list to create it in Polylang settings + * + * @param {object} language The language object + */ + function addLanguage( language ) { + // language properties come from the select dropdown which is built server side and well escaped. + // see template view-wizard-step-languages.php. + var languageValueHtml = $( '' ).text( language.text ).prepend( language.flagUrl ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageTrashIconHtml = $( '' ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'dashicons dashicons-trash' ) + .attr( 'data-language', language.locale ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'screen-reader-text' ) + .text( pll_wizard_params.i18n_remove_language_icon ) + ) + ); + // see the comment and the hardcoded code above. languageTrashIconHtml and languageValueHtml are safe. + var languageLineHtml = $( '' ).prepend( languageTrashIconHtml ).prepend( languageValueHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageFieldHtml = $( '' ).attr( + { + type: 'hidden', + name: 'languages[]' + } + ).val( language.locale ); + + languagesList.val( '' ); + languagesList.selectmenu( 'refresh' ); // Refresh jQuery selectmenu widget after changing the value. + + languagesMap.set( language.locale, language ); + + // see above how languageLineHtml is built. + languagesListTable.append( languageLineHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + // Bind click event on trash icon. + languagesListTable.on( + 'click', + 'span[data-language=' + language.locale + ']', + function ( event ) { + event.preventDefault(); + // Remove line in languages table. + $( this ).parents( 'tr' ).remove(); + // Remove input field. + var languageField = languageFields.children( 'input[value=' + $( this ).data( 'language' ) + ']' ).remove(); + // If there is no more languages hide languages table. + if ( languagesListTable.children().length <= 0 ) { + languagesTable.hide(); + } + // Remove language from the Map. + languagesMap.delete( $( this ).data( 'language' ) ); + // Hide error message. + hideError(); + } + ); + // see above how languageFieldHtml is built. + // Add hidden input field for posting the form. + languageFields.append( languageFieldHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + } + + /** + * Display an error message + * + * @param {string} message The message to display + */ + function showError( message ) { + messagesContainer.empty(); + // html is hardcoded and use of jQuery text method which is safe to add message value. + // In addition message is i18n value which is initialized server side in PLL_Wizard::add_step_languages and correctly escaped. + messagesContainer.prepend( $( '

    ' ).addClass( 'error' ).text( message ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + + /** + * Hide all error messages and fields in error + */ + function hideError() { + messagesContainer.empty(); + addLanguageForm.find( '.error' ).removeClass( 'error field-in-error' ); + } + + /** + * Style the field to indicate where the error is + * + * @param {object} field The jQuery element which is in error + */ + function showFieldInError( field ) { + field.addClass( 'error field-in-error' ); + } + + /** + * Focus on a specific element + * + * @param {object} field The jQuery element which will be focused + */ + function focusOnField( field ) { + field.trigger( 'focus' ); + } + + /** + * Disable a specific button + * + * @param {object} button + */ + function disableButton( button ){ + button.prop( 'disabled', true ); + // Because the button is disabled we need to add the value of the button to ensure it will pass in the request. + addLanguageForm.append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ).prop( + { + type: 'hidden', + name: button.prop( 'name' ), + value: button.prop( 'value' ) + } + ) + ); + } + + /** + * Remove error when a new selection is done in languages list. + */ + languagesList.on( + 'selectmenuchange', + function () { + hideError();; + } + ); + /** + * Bind click event on "Add language" button + */ + $( '#add-language' ).on( + 'click', + function ( event ) { + hideError(); + var selectedOption = event.currentTarget.form.lang_list.options[event.currentTarget.form.lang_list.selectedIndex]; + if ( '' !== selectedOption.value && ! languagesMap.has( selectedOption.value ) ) { + addLanguage( + { + locale: selectedOption.value, + text: selectedOption.innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + // Show table of languages. + languagesTable.show(); + // Put back the focus on the select language field after clicking on "Add language button". + focusOnField( $( '#lang_list-button' ) ); + } else { + var message = pll_wizard_params.i18n_no_language_selected; + if ( languagesMap.has( selectedOption.value ) ) { + message = pll_wizard_params.i18n_language_already_added; + } + showError( message ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + + } + } + ); + + /** + * Bind submit event on "add_lang" form + */ + addLanguageForm.on( + 'submit', + function ( event ) { + // Verify if there is at least one language. + var isLanguagesAlreadyDefined = definedLanguagesListTable.children().length > 0; + var selectedLanguage = $( '#lang_list' ).val(); + if ( languagesMap.size <= 0 && ! isLanguagesAlreadyDefined ) { + if ( '' === selectedLanguage ) { + showError( pll_wizard_params.i18n_no_language_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } else { + showError( pll_wizard_params.i18n_add_language_needed ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#add-language' ) ); // Put the focus on the "Add language" button. + } + return false; + } + // Verify if the language has been added in the list otherwise display a dialog box to confirm what to do. + if ( '' !== selectedLanguage ) { + // Verify we don't add a duplicate language before opening the dialog box otherwise display an error message. + if ( ! languagesMap.has( selectedLanguage ) ) { + dialog.dialog( 'open' ); + } else { + showError( pll_wizard_params.i18n_language_already_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } + return false; + } + disableButton( nextStepButton ); + } + ); + + // Is there an error return by PHP ? + var searchParams = new URLSearchParams( document.location.search ); + if ( searchParams.has( 'activate_error' ) ) { + // If the error code exists, display it. + if ( undefined !== pll_wizard_params[ searchParams.get( 'activate_error' ) ] ) { + showError( pll_wizard_params[ searchParams.get( 'activate_error' ) ] ); + } + } + + function confirmDialog( what ) { + switch ( what ) { + case 'yes': + var selectedOption = $( '#lang_list' ).children( ':selected' ); + addLanguage( + { + locale: selectedOption[0].value, + text: selectedOption[0].innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + break; + case 'no': + // Empty select form field and submit again the form. + languagesList.val( '' ); + break; + case 'ignore': + } + dialog.dialog( 'close' ); + if ( 'ignore' === what ) { + focusOnField( $( '#lang_list-button' ) ); + } else { + addLanguageForm.submit(); + } + } + + // Initialize dialog box in the case a language is selected but not added in the list. + dialog.dialog( + { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: pll_wizard_params.i18n_dialog_title, + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( $( 'body' ).hasClass( 'rtl' ) ) { + $( this ).parent().css( + { + right: $( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + // Display language name and flag information in dialog box. + $( this ).find( '#dialog-language' ).text( $( '#lang_list' ).children( ':selected' ).first().text() ); + // language properties come from the select dropdown #lang_list which is built server side and well escaped. + // see template view-wizard-step-languages.php. + $( this ).find( '#dialog-language-flag' ).empty().prepend( $( '#lang_list' ).children( ':selected' ).data( 'flag-html' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + }, + buttons: [ + { + text: pll_wizard_params.i18n_dialog_yes_button, + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_no_button, + click: function ( event ) { + confirmDialog( 'no' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_ignore_button, + click: function ( event ) { + confirmDialog( 'ignore' ); + } + } + ] + } + ) + } +); + diff --git a/wp-content/plugins/polylang/js/build/languages-step.min.js b/wp-content/plugins/polylang/js/build/languages-step.min.js new file mode 100644 index 0000000..597512d --- /dev/null +++ b/wp-content/plugins/polylang/js/build/languages-step.min.js @@ -0,0 +1 @@ +jQuery(function(a){var e=a(".languages-step"),n=a("#language-fields"),t=a("#languages"),l=a("#languages tbody"),i=a("#defined-languages tbody"),r=a("#lang_list"),d=a('[name="save_step"]'),s=a("#messages"),o=new Map,g=a("#dialog");function u(e){var i=a("").text(e.text).prepend(e.flagUrl),d=a("").append(a("").addClass("dashicons dashicons-trash").attr("data-language",e.locale).append(a("").addClass("screen-reader-text").text(pll_wizard_params.i18n_remove_language_icon))),s=a("").prepend(d).prepend(i),g=a("").attr({type:"hidden",name:"languages[]"}).val(e.locale);r.val(""),r.selectmenu("refresh"),o.set(e.locale,e),l.append(s),l.on("click","span[data-language="+e.locale+"]",function(e){e.preventDefault(),a(this).parents("tr").remove();n.children("input[value="+a(this).data("language")+"]").remove();l.children().length<=0&&t.hide(),o.delete(a(this).data("language")),c()}),n.append(g)}function p(e){s.empty(),s.prepend(a("

    ").addClass("error").text(e))}function c(){s.empty(),e.find(".error").removeClass("error field-in-error")}function _(a){a.addClass("error field-in-error")}function m(a){a.trigger("focus")}r.on("selectmenuchange",function(){c()}),a("#add-language").on("click",function(e){c();var n=e.currentTarget.form.lang_list.options[e.currentTarget.form.lang_list.selectedIndex];if(""===n.value||o.has(n.value)){var l=pll_wizard_params.i18n_no_language_selected;o.has(n.value)&&(l=pll_wizard_params.i18n_language_already_added),p(l),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))}else u({locale:n.value,text:n.innerText,name:a(n).data("language-name"),flagUrl:a(n).data("flag-html")}),t.show(),m(a("#lang_list-button"))}),e.on("submit",function(n){var t,l=i.children().length>0,s=a("#lang_list").val();return o.size<=0&&!l?(""===s?(p(pll_wizard_params.i18n_no_language_added),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))):(p(pll_wizard_params.i18n_add_language_needed),_(r.next("span.ui-selectmenu-button")),m(a("#add-language"))),!1):""!==s?(o.has(s)?(p(pll_wizard_params.i18n_language_already_added),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))):g.dialog("open"),!1):((t=d).prop("disabled",!0),void e.append(a("").prop({type:"hidden",name:t.prop("name"),value:t.prop("value")})))});var f=new URLSearchParams(document.location.search);function h(n){switch(n){case"yes":var t=a("#lang_list").children(":selected");u({locale:t[0].value,text:t[0].innerText,name:a(t).data("language-name"),flagUrl:a(t).data("flag-html")});break;case"no":r.val("")}g.dialog("close"),"ignore"===n?m(a("#lang_list-button")):e.submit()}f.has("activate_error")&&void 0!==pll_wizard_params[f.get("activate_error")]&&p(pll_wizard_params[f.get("activate_error")]),g.dialog({autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:pll_wizard_params.i18n_dialog_title,minWidth:600,maxWidth:"100%",open:function(e,n){a("body").hasClass("rtl")&&a(this).parent().css({right:a(this).parent().css("left"),left:"auto"}),a(this).find("#dialog-language").text(a("#lang_list").children(":selected").first().text()),a(this).find("#dialog-language-flag").empty().prepend(a("#lang_list").children(":selected").data("flag-html"))},buttons:[{text:pll_wizard_params.i18n_dialog_yes_button,click:function(a){h("yes")}},{text:pll_wizard_params.i18n_dialog_no_button,click:function(a){h("no")}},{text:pll_wizard_params.i18n_dialog_ignore_button,click:function(a){h("ignore")}}]})}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/media.js b/wp-content/plugins/polylang/js/build/media.js new file mode 100644 index 0000000..a642448 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/media.js @@ -0,0 +1,20 @@ +/** + * @package Polylang + */ + +/** + * Media list table + * When clicking on attach link, filters find post list per media language + */ +jQuery( + function ( $ ) { + $.ajaxPrefilter( + function ( options, originalOptions, jqXHR ) { + if ( 'string' === typeof options.data && -1 !== options.data.indexOf( 'action=find_posts' ) ) { + options.data = 'pll_post_id=' + $( '#affected' ).val() + '&' + options.data; + } + } + ); + } +); + diff --git a/wp-content/plugins/polylang/js/build/media.min.js b/wp-content/plugins/polylang/js/build/media.min.js new file mode 100644 index 0000000..1e35f75 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/media.min.js @@ -0,0 +1 @@ +jQuery(function(a){a.ajaxPrefilter(function(t,d,f){"string"==typeof t.data&&-1!==t.data.indexOf("action=find_posts")&&(t.data="pll_post_id="+a("#affected").val()+"&"+t.data)})}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/nav-menu.js b/wp-content/plugins/polylang/js/build/nav-menu.js new file mode 100644 index 0000000..118312c --- /dev/null +++ b/wp-content/plugins/polylang/js/build/nav-menu.js @@ -0,0 +1,220 @@ +/** + * Handles the options in the language switcher nav menu metabox. + * + * @package Polylang + */ + +const pllNavMenu = { + /** + * The element wrapping the menu elements. + * + * @member {HTMLElement|null} + */ + wrapper: null, + + /** + * Init. + */ + init: () => { + if ( document.readyState !== 'loading' ) { + pllNavMenu.ready(); + } else { + document.addEventListener( 'DOMContentLoaded', pllNavMenu.ready ); + } + }, + + /** + * Called when the DOM is ready. Attaches the events to the wrapper. + */ + ready: () => { + pllNavMenu.wrapper = document.getElementById( 'menu-to-edit' ); + + if ( ! pllNavMenu.wrapper ) { + return; + } + + pllNavMenu.wrapper.addEventListener( 'click', pllNavMenu.printMetabox ); + pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.ensureContent ); + pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.showHideRows ); + }, + + printMetabox: { + /** + * Event callback that prints our checkboxes in the language switcher. + * + * @param {Event} event The event. + */ + handleEvent: ( event ) => { + if ( ! event.target.classList.contains( 'item-edit' ) ) { + // Not clicking on a Edit arrow button. + return; + } + + const metabox = event.target.closest( '.menu-item' ).querySelector( '.menu-item-settings' ); + + if ( ! metabox?.id ) { + // Should not happen. + return; + } + + if ( ! metabox.querySelectorAll( 'input[value="#pll_switcher"][type=text]' ).length ) { + // Not our metabox, or already replaced. + return; + } + + // Remove default fields we don't need. + [ ...metabox.children ].forEach( ( el ) => { + if ( 'P' === el.nodeName && ! el.classList.contains( 'field-move' ) ) { + el.remove(); + } + } ); + + const t = pllNavMenu.printMetabox; + const itemId = Number( metabox.id.replace( 'menu-item-settings-', '' ) ); + + metabox.append( t.createHiddenInput( 'title', itemId, pll_data.title ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + metabox.append( t.createHiddenInput( 'url', itemId, '#pll_switcher' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + metabox.append( t.createHiddenInput( 'pll-detect', itemId, 1 ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + const ids = Array( 'hide_if_no_translation', 'hide_current', 'force_home', 'show_flags', 'show_names', 'dropdown' ); // Reverse order. + const isValDefined = typeof( pll_data.val[ itemId ] ) !== 'undefined'; + + ids.forEach( ( optionName ) => { + // Create the checkbox's wrapper. + const inputWrapper = t.createElement( 'p', { class: 'description' } ); + + if ( 'hide_current' === optionName && isValDefined && 1 === pll_data.val[ itemId ].dropdown ) { + // Hide the `hide_current` checkbox if `dropdown` is checked. + inputWrapper.classList.add( 'hidden' ); + } + + metabox.prepend( inputWrapper ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + + // Create the checkbox's label. + const inputId = `edit-menu-item-${ optionName }-${ itemId }`; + const label = t.createElement( 'label', { 'for': inputId } ); + label.innerText = ` ${ pll_data.strings[ optionName ] }`; + + inputWrapper.append( label ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + // Create the checkbox. + const cb = t.createElement( 'input', { + type: 'checkbox', + id: inputId, + name: `menu-item-${ optionName }[${ itemId }]`, + value: 1, + } ); + + if ( ( isValDefined && 1 === pll_data.val[ itemId ][ optionName ] ) || ( ! isValDefined && 'show_names' === optionName ) ) { // `show_names` as default value. + cb.checked = true; + } + + label.prepend( cb ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } ); + }, + + /** + * Creates and returns a `` element. + * + * @param {string} id An identifier for this input. It will be part of the final `id` attribute. + * @param {number} itemId The ID of the menu element (post ID). + * @param {string|number} value The input's value. + * @return {HTMLElement} The input element. + */ + createHiddenInput: ( id, itemId, value ) => { + return pllNavMenu.printMetabox.createElement( 'input', { + type: 'hidden', + id: `edit-menu-item-${ id }-${ itemId }`, + name: `menu-item-${ id }[${ itemId }]`, + value: value, + } ); + }, + + /** + * Creates and returns an element. + * + * @param {string} type Element's type. + * @param {Object} atts Element's attributes. + * @return {HTMLElement} The element. + */ + createElement: ( type, atts ) => { + const el = document.createElement( type ); + for ( const [ key, value ] of Object.entries( atts ) ) { + el.setAttribute( key, value ); + } + return el; + }, + }, + + ensureContent: { + regExpr: new RegExp( /^edit-menu-item-show_(names|flags)-(\d+)$/ ), + + /** + * Event callback that disallows unchecking both `show_names` and `show_flags`. + * + * @param {Event} event The event. + */ + handleEvent: ( event ) => { + if ( ! event.target.id || event.target.checked ) { + // Now checked, nothing to do. + return; + } + + const matches = event.target.id.match( pllNavMenu.ensureContent.regExpr ); + + if ( ! matches ) { + // Not the checkbox we want. + return; + } + + // Check the other checkbox. + const [ , type, id ] = matches; + const otherType = 'names' === type ? 'flags' : 'names'; + document.getElementById( `edit-menu-item-show_${ otherType }-${ id }` ).checked = true; + }, + }, + + showHideRows: { + regExpr: new RegExp( /^edit-menu-item-dropdown-(\d+)$/ ), + + /** + * Event callback that shows or hides the `hide_current` checkbox when `dropdown` is checked. + * + * @param {Event} event The event. + */ + handleEvent: ( event ) => { + if ( ! event.target.id ) { + // Not the checkbox we want. + return; + } + + const matches = event.target.id.match( pllNavMenu.showHideRows.regExpr ); + + if ( ! matches ) { + // Not the checkbox we want. + return; + } + + const hideCb = document.getElementById( `edit-menu-item-hide_current-${ matches[1] }` ); + + if ( ! hideCb ) { + // Should not happen. + return; + } + + const description = hideCb.closest( '.description' ); + + // Hide or show. + description.classList.toggle( 'hidden', event.target.checked ); + + if ( event.target.checked ) { + // Uncheck after hiding. + hideCb.checked = false; + hideCb.dispatchEvent( new Event( 'change' ) ); + } + }, + }, +}; + +pllNavMenu.init(); + diff --git a/wp-content/plugins/polylang/js/build/nav-menu.min.js b/wp-content/plugins/polylang/js/build/nav-menu.min.js new file mode 100644 index 0000000..da7c1f0 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/nav-menu.min.js @@ -0,0 +1 @@ +const pllNavMenu={wrapper:null,init:()=>{"loading"!==document.readyState?pllNavMenu.ready():document.addEventListener("DOMContentLoaded",pllNavMenu.ready)},ready:()=>{pllNavMenu.wrapper=document.getElementById("menu-to-edit"),pllNavMenu.wrapper&&(pllNavMenu.wrapper.addEventListener("click",pllNavMenu.printMetabox),pllNavMenu.wrapper.addEventListener("change",pllNavMenu.ensureContent),pllNavMenu.wrapper.addEventListener("change",pllNavMenu.showHideRows))},printMetabox:{handleEvent:e=>{if(!e.target.classList.contains("item-edit"))return;const t=e.target.closest(".menu-item").querySelector(".menu-item-settings");if(!t?.id)return;if(!t.querySelectorAll('input[value="#pll_switcher"][type=text]').length)return;[...t.children].forEach(e=>{"P"!==e.nodeName||e.classList.contains("field-move")||e.remove()});const n=pllNavMenu.printMetabox,a=Number(t.id.replace("menu-item-settings-",""));t.append(n.createHiddenInput("title",a,pll_data.title)),t.append(n.createHiddenInput("url",a,"#pll_switcher")),t.append(n.createHiddenInput("pll-detect",a,1));const r=Array("hide_if_no_translation","hide_current","force_home","show_flags","show_names","dropdown"),d=void 0!==pll_data.val[a];r.forEach(e=>{const r=n.createElement("p",{class:"description"});"hide_current"===e&&d&&1===pll_data.val[a].dropdown&&r.classList.add("hidden"),t.prepend(r);const l=`edit-menu-item-${e}-${a}`,i=n.createElement("label",{for:l});i.innerText=` ${pll_data.strings[e]}`,r.append(i);const c=n.createElement("input",{type:"checkbox",id:l,name:`menu-item-${e}[${a}]`,value:1});(d&&1===pll_data.val[a][e]||!d&&"show_names"===e)&&(c.checked=!0),i.prepend(c)})},createHiddenInput:(e,t,n)=>pllNavMenu.printMetabox.createElement("input",{type:"hidden",id:`edit-menu-item-${e}-${t}`,name:`menu-item-${e}[${t}]`,value:n}),createElement:(e,t)=>{const n=document.createElement(e);for(const[e,a]of Object.entries(t))n.setAttribute(e,a);return n}},ensureContent:{regExpr:new RegExp(/^edit-menu-item-show_(names|flags)-(\d+)$/),handleEvent:e=>{if(!e.target.id||e.target.checked)return;const t=e.target.id.match(pllNavMenu.ensureContent.regExpr);if(!t)return;const[,n,a]=t,r="names"===n?"flags":"names";document.getElementById(`edit-menu-item-show_${r}-${a}`).checked=!0}},showHideRows:{regExpr:new RegExp(/^edit-menu-item-dropdown-(\d+)$/),handleEvent:e=>{if(!e.target.id)return;const t=e.target.id.match(pllNavMenu.showHideRows.regExpr);if(!t)return;const n=document.getElementById(`edit-menu-item-hide_current-${t[1]}`);if(!n)return;n.closest(".description").classList.toggle("hidden",e.target.checked),e.target.checked&&(n.checked=!1,n.dispatchEvent(new Event("change")))}}};pllNavMenu.init(); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/post.js b/wp-content/plugins/polylang/js/build/post.js new file mode 100644 index 0000000..b60b559 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/post.js @@ -0,0 +1,178 @@ +/** + * @package Polylang + */ + +/** + * Tag suggest in quick edit + */ +jQuery( + function ( $ ) { + $.ajaxPrefilter( + function ( options, originalOptions, jqXHR ) { + if ( 'string' === typeof options.data && -1 !== options.data.indexOf( 'action=ajax-tag-search' ) && ( lang = $( ':input[name="inline_lang_choice"]' ).val() ) ) { + options.data = 'lang=' + lang + '&' + options.data; + } + } + ); + } +); + +/** + * Quick edit + */ +jQuery( + function ( $ ) { + const handleQuickEditInsertion = ( mutationsList ) => { + for ( const mutation of mutationsList ) { + const addedNodes = Array.from( mutation.addedNodes ).filter( el => el.nodeType === Node.ELEMENT_NODE ) + const form = addedNodes[0]; + if ( 0 < mutation.addedNodes.length && form.classList.contains( 'inline-editor' ) ) { + // WordPress has inserted the quick edit form. + const post_id = Number( form.id.substring( 5 ) ); + + if ( post_id > 0 ) { + // Get the language dropdown. + const select = form.querySelector( 'select[name="inline_lang_choice"]' ); + const lang = document.querySelector( '#lang_' + String( post_id ) ).innerHTML; + select.value = lang; // Populates the dropdown with the post language. + + filter_terms( lang ); // Initial filter for category checklist. + filter_pages( lang ); // Initial filter for parent dropdown. + + // Modify category checklist and parent dropdown on language change. + select.addEventListener( + 'change', + function ( event ) { + const newLang = event.target.value; + filter_terms( newLang ); + filter_pages( newLang ); + } + ); + } + } + /** + * Filters the category checklist. + */ + function filter_terms( lang ) { + if ( "undefined" != typeof( pll_term_languages ) ) { + $.each( + pll_term_languages, + function ( lg, term_tax ) { + $.each( + term_tax, + function ( tax, terms ) { + $.each( + terms, + function ( i ) { + // Backward compatibility with WordPress < 6.7. + // Support both old (WP < 6.7) and new (WP >= 6.7) ID formats. + // Old format: category-123 (WordPress < 6.7). + // New format: in-category-123-1 (WordPress >= 6.7). + const termId = pll_term_languages[ lg ][ tax ][ i ]; + const selector = `#${tax}-${termId}, [id^="in-${tax}-${termId}-"]`; + $( selector ).toggle( lang === lg ); + } + ); + } + ); + } + ); + } + } + + /** + * Filters the parent page dropdown list. + */ + function filter_pages( lang ) { + if ( "undefined" != typeof( pll_page_languages ) ) { + $.each( + pll_page_languages, + function ( lg, pages ) { + $.each( + pages, + function ( i ) { + v = $( '#post_parent option[value="' + pll_page_languages[ lg ][ i ] + '"]' ); + lang == lg ? v.show() : v.hide(); + } + ); + } + ); + } + } + } + } + const table = document.getElementById( 'the-list' ); + + if ( ! table ) { + return; + } + + const config = { childList: true, subtree: true }; + const observer = new MutationObserver( handleQuickEditInsertion ); + + observer.observe( table, config); + } +); + +/** + * Update rows of translated posts when the language is modified in quick edit + * Acts on ajaxSuccess event + */ +jQuery( + function ( $ ) { + $( document ).ajaxSuccess( + function ( event, xhr, settings ) { + function update_rows( post_id ) { + // collect old translations + var translations = new Array(); + $( '.translation_' + post_id ).each( + function () { + translations.push( $( this ).parent().parent().attr( 'id' ).substring( 5 ) ); + } + ); + + var data = { + action: 'pll_update_post_rows', + post_id: post_id, + translations: translations.join( ',' ), + post_type: $( "input[name='post_type']" ).val(), + screen: $( "input[name='screen']" ).val(), + _pll_nonce: $( "input[name='_inline_edit']" ).val() // reuse quick edit nonce + }; + + // get the modified rows in ajax and update them + $.post( + ajaxurl, + data, + function ( response ) { + if ( response ) { + // Since WP changeset #52710 parseAjaxResponse() return content to notice the user in a HTML tag with ajax-response id. + // Not to disturb this behaviour by executing another ajax request in the ajaxSuccess event, we need to target another unexisting id. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + if ( 'row' == this.what ) { + // data is built with a call to WP_Posts_List_Table::single_row method + // which uses internally other WordPress methods which escape correctly values. + // For Polylang language columns the HTML code is correctly escaped in PLL_Admin_Filters_Columns::post_column method. + $( "#post-" + this.supplemental.post_id ).replaceWith( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + } + } + ); + } + } + ); + } + + if ( 'string' == typeof( settings.data ) ) { // Need to check the type due to block editor sometime sending FormData objects + var data = wpAjax.unserialize( settings.data ); // what were the data sent by the ajax request? + if ( 'undefined' != typeof( data['action'] ) && 'inline-save' == data['action'] ) { + update_rows( data['post_ID'] ); + } + } + } + ); + } +); + diff --git a/wp-content/plugins/polylang/js/build/post.min.js b/wp-content/plugins/polylang/js/build/post.min.js new file mode 100644 index 0000000..855099a --- /dev/null +++ b/wp-content/plugins/polylang/js/build/post.min.js @@ -0,0 +1 @@ +jQuery(function(n){n.ajaxPrefilter(function(e,t,a){"string"==typeof e.data&&-1!==e.data.indexOf("action=ajax-tag-search")&&(lang=n(':input[name="inline_lang_choice"]').val())&&(e.data="lang="+lang+"&"+e.data)})}),jQuery(function(n){const e=document.getElementById("the-list");if(!e)return;new MutationObserver(e=>{for(const i of e){const o=Array.from(i.addedNodes).filter(n=>n.nodeType===Node.ELEMENT_NODE)[0];if(00){const l=o.querySelector('select[name="inline_lang_choice"]'),c=document.querySelector("#lang_"+String(s)).innerHTML;l.value=c,t(c),a(c),l.addEventListener("change",function(n){const e=n.target.value;t(e),a(e)})}}function t(e){"undefined"!=typeof pll_term_languages&&n.each(pll_term_languages,function(t,a){n.each(a,function(a,i){n.each(i,function(i){const o=pll_term_languages[t][a][i];n(`#${a}-${o}, [id^="in-${a}-${o}-"]`).toggle(e===t)})})})}function a(e){"undefined"!=typeof pll_page_languages&&n.each(pll_page_languages,function(t,a){n.each(a,function(a){v=n('#post_parent option[value="'+pll_page_languages[t][a]+'"]'),e==t?v.show():v.hide()})})}}}).observe(e,{childList:!0,subtree:!0})}),jQuery(function(n){n(document).ajaxSuccess(function(e,t,a){if("string"==typeof a.data){var i=wpAjax.unserialize(a.data);void 0!==i.action&&"inline-save"==i.action&&function(e){var t=new Array;n(".translation_"+e).each(function(){t.push(n(this).parent().parent().attr("id").substring(5))});var a={action:"pll_update_post_rows",post_id:e,translations:t.join(","),post_type:n("input[name='post_type']").val(),screen:n("input[name='screen']").val(),_pll_nonce:n("input[name='_inline_edit']").val()};n.post(ajaxurl,a,function(e){if(e){var t=wpAjax.parseAjaxResponse(e,"pll-ajax-response");n.each(t.responses,function(){"row"==this.what&&n("#post-"+this.supplemental.post_id).replaceWith(this.data)})}})}(i.post_ID)}})}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/settings.js b/wp-content/plugins/polylang/js/build/settings.js new file mode 100644 index 0000000..f4f63bb --- /dev/null +++ b/wp-content/plugins/polylang/js/build/settings.js @@ -0,0 +1,435 @@ +/** + * @package Polylang + */ + +jQuery( + function ( $ ) { + + // languages list table + // accessibility to row actions on focus + // mainly copy paste of WP code from common.js + var transitionTimeout; + $( 'table.languages' ).on( + { // restricted to languages list table + focusin: function () { + clearTimeout( transitionTimeout ); + var focusedRowActions = $( this ).find( '.row-actions' ); + // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help. + $( '.row-actions' ).not( this ).removeClass( 'visible' ); + focusedRowActions.addClass( 'visible' ); + }, + focusout: function () { + // Tabbing between post title and .row-actions links needs a brief pause, otherwise + // the .row-actions div gets hidden in transit in some browsers ( ahem, Firefox ). + transitionTimeout = setTimeout( + function () { + focusedRowActions.removeClass( 'visible' ); + }, + 30 + ); + } + }, + 'tr' + ); // acts on the whole tr instead of single td as we have actions links in several columns + + /** + * Common functions and variables for overriding languages and flags dropdown list by a jQuery UI selectmenu widget. + */ + + // Add a boolean variable to be able to check jQuery UI >= 1.12 which is introduced in WP 5.6. + // Backward compatibility WP < 5.6 + var isJqueryUImin112 = $.ui.version >= '1.12.0'; + // Allow to check if a flag list dropdown is present. Not present in the Wizard steps or other settings page. + var flagListExist = $( "#flag_list" ).length; + // Allow to check if a language list dropdown is present. Not present in other settings page. + var langListExist = $( "#lang_list" ).length; + // jQuery UI selectmenu widget width option + var defaultSelectmenuWidth = '95%'; + var wizardSelectmenuWidth = '100%'; + + // Inject flag image when jQuery UI selectmenu is created or an item is selected. + // jQuery UI 1.12 introduce a wrapper inside de li tag which is necessary to selectmenu widget to work correctly. + // Mainly copy from the original jQuery UI 1.12 selectmenu widget _renderItem method. + // Note this code works fine with jQuery UI 1.11.4 too. + var selectmenuRenderItem = function ( ul, item ) { + var li = $( '

  • ' ); + var wrapper = $( '
    '); + + if ( item.disabled ) { + this._addClass( li, null, "ui-state-disabled" ); + } + // `item.label` is the original `
  • "),s=e("
    ");return n.disabled&&this._addClass(l,null,"ui-state-disabled"),this._setText(s,n.label),s.prepend(e(n.element).data("flag-html")),s.children("img").addClass("ui-icon"),l.append(s).appendTo(t)},a=function(t){var n=e(t).selectmenu("instance").buttonText;n.prepend(e(t).children(":selected").data("flag-html")),n.children("img").addClass("ui-icon")},c=function(t){var n=e("");return this._setText(n,t.label),this._addClass(n,"ui-selectmenu-text"),n.prepend(e(t.element).data("flag-html")),n.children("img").addClass("ui-icon"),n};function o(e,t){var l=e.selectmenu(t).selectmenu("instance");return l._renderItem=i,n&&(l._renderButtonItem=c,l.refresh()),l}var r={width:"95%",classes:{"ui-selectmenu-menu":"pll-selectmenu-menu","ui-selectmenu-button":"pll-selectmenu-button"}},u={},d=function(e,t){a(e.target)},p=function(t,n){a(e(t.target).selectmenu("refresh"))},g=function(t,n){e(t.target).selectmenu("refresh")};if(u=n?{change:g,open:g}:{create:d,select:d,change:p,open:p},l){var h=o(e("#flag_list"),Object.assign({},r,u));e("#lang_list").on("languageChanged",function(e,t){h.element.val(t),h._trigger("change")})}var f=function(t,n){var l=function(t){var n=e("option:selected",t.target),l=n.val().split(":");return{slug:l[0],locale:l[1],rtl:[l[2]],flag:l[3],name:n.text().split(" - ")[0]}}(t);!function(t){e("#lang_slug").val(t.slug),e("#lang_locale").val(t.locale),e('input[name="rtl"]').val(t.rtl),e("#lang_name").val(t.name)}(l),e(t.target).trigger("languageChanged",l.flag)},v={};e("#lang_list").closest(".pll-wizard-content").length>0&&(r=Object.assign(r,{width:"100%"})),v=n?{change:f}:{create:d,select:d,change:f},s&&o(e("#lang_list"),Object.assign({},r,v)),e(".translation input").on("keydown",function(t){"Enter"===t.key&&(t.preventDefault(),e("#submit").trigger("click"))}),e("#the-list").on("click",".configure>a",function(){return e(".pll-configure").hide().prev().show(),e(this).closest("tr").hide().next().show(),!1}),e("#the-list").on("click",".cancel",function(){e(this).closest("tr").hide().prev().show()}),e("#the-list").on("click",".save",function(){var t=e(this).closest("tr"),n=t.attr("id").split("-"),l={action:"pll_save_options",pll_ajax_settings:!0,module:n[n.length-1],_pll_nonce:e("#_pll_nonce").val()};l=t.find(":input").serialize()+"&"+e.param(l),e.post(ajaxurl,l,function(n){var l=wpAjax.parseAjaxResponse(n,"pll-ajax-response");e.each(l.responses,function(){switch(wp.hooks.doAction("pll_settings_saved",this,t.get(0)),this.what){case"license-update":e("#pll-license-"+this.data).replaceWith(this.supplemental.html);break;case"success":t.hide().prev().show();case"error":e(".settings-error").remove(),e("h1").after(this.data),e(".notice.is-dismissible").each(function(){var t=e(this),n=e(''),l=pll_settings.dismiss_notice||"";n.find(".screen-reader-text").text(l),t.append(n),n.on("click.wp-dismiss-notice",function(n){n.preventDefault(),t.fadeTo(100,0,function(){e(this).slideUp(100,function(){e(this).remove()})})})})}})})}),e(".pll-configure").on("keydown",function(t){"Enter"===t.key&&(t.preventDefault(),e(this).find(".save").trigger("click")),"Escape"===t.key&&(t.preventDefault(),e(this).find(".cancel").trigger("click"))}),e("input[name='force_lang']").on("change",function(){function t(e,t){t?e.show():e.hide()}var n=e(this).val();t(e("#pll-domains-table"),3==n),t(e("#pll-hide-default"),3>n),t(e("#pll-rewrite"),2>n),t(e("#pll-redirect-lang"),2>n)}),e(".pll-deactivate-license").on("click",function(){var t={action:"pll_deactivate_license",pll_ajax_settings:!0,id:e(this).attr("id"),_pll_nonce:e("#_pll_nonce").val()};e.post(ajaxurl,t,function(t){e("#pll-license-"+t.id).replaceWith(t.html)})}),e(".if-js-closed").removeClass("if-js-closed").addClass("closed"),"undefined"!=typeof postboxes&&postboxes.add_postbox_toggles(pagenow)}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/term.js b/wp-content/plugins/polylang/js/build/term.js new file mode 100644 index 0000000..5075725 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/term.js @@ -0,0 +1,250 @@ +/** + * @package Polylang + */ + +/** + * Quick edit + */ +jQuery( + function ( $ ) { + const handleQuickEditInsertion = ( mutationsList ) => { + for ( const mutation of mutationsList ) { + const addedNodes = Array.from( mutation.addedNodes ).filter( el => el.nodeType === Node.ELEMENT_NODE ) + const form = addedNodes[0]; + if ( 0 < mutation.addedNodes.length && form.classList.contains( 'inline-edit-row' ) ) { + // WordPress has inserted the quick edit form. + const term_id = Number( form.id.substring( 5 ) ); + + if ( term_id > 0 ) { + // Get the language dropdown. + const select = form.querySelector( 'select[name="inline_lang_choice"]' ); + const lang = document.querySelector( '#lang_' + String( term_id ) ).innerHTML; + select.value = lang; // Populates the dropdown with the post language. + + // Disable the language dropdown for default categories. + const default_cat = document.querySelector( `#default_cat_${term_id}` )?.innerHTML; + if ( term_id == default_cat ) { + select.disabled = true; + } + } + } + } + } + const table = document.getElementById( 'the-list' ); + if ( null !== table ) { + // Ensure the table is displayed before listening to any change. + const config = { childList: true, subtree: true }; + const observer = new MutationObserver( handleQuickEditInsertion ); + + observer.observe( table, config); + } + } +); + +/** + * Update rows of translated terms when adding / deleting a translation or when the language is modified in quick edit. + * Acts on ajaxSuccess event. + */ +jQuery( + function ( $ ) { + $( document ).ajaxSuccess( + function ( event, xhr, settings ) { + function update_rows( term_id ) { + // collect old translations + var translations = new Array(); + $( '.translation_' + term_id ).each( + function () { + translations.push( $( this ).parent().parent().attr( 'id' ).substring( 4 ) ); + } + ); + + var data = { + action: 'pll_update_term_rows', + term_id: term_id, + translations: translations.join( ',' ), + taxonomy: $( "input[name='taxonomy']" ).val(), + post_type: $( "input[name='post_type']" ).val(), + screen: $( "input[name='screen']" ).val(), + _pll_nonce: $( '#_pll_nonce' ).val() + }; + + // get the modified rows in ajax and update them + $.post( + ajaxurl, + data, + function ( response ) { + if ( response ) { + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + if ( 'row' == this.what ) { + // data is built with a call to WP_Terms_List_Table::single_row method + // which uses internally other WordPress methods which escape correctly values. + // For Polylang language columns the HTML code is correctly escaped in PLL_Admin_Filters_Columns::term_column method. + $( "#tag-" + this.supplemental.term_id ).replaceWith( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + } + } + ); + } + } + ); + } + + var data = wpAjax.unserialize( settings.data ); // what were the data sent by the ajax request? + if ( 'undefined' != typeof( data['action'] ) ) { + switch ( data['action'] ) { + // when adding a term, the new term_id is in the ajax response + case 'add-tag': + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + res = wpAjax.parseAjaxResponse( xhr.responseXML, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + if ( 'term' == this.what ) { + update_rows( this.supplemental.term_id ); + } + } + ); + + // and also reset translations hidden input fields + $( '.htr_lang' ).val( 0 ); + break; + + // when deleting a term + case 'delete-tag': + update_rows( data['tag_ID'] ); + break; + + // in case the language is modified in quick edit and breaks translations + case 'inline-save-tax': + update_rows( data['tax_ID'] ); + break; + } + } + } + ); + } +); + +jQuery( + function ( $ ) { + // translations autocomplete input box + function init_translations() { + $( '.tr_lang' ).each( + function () { + var tr_lang = $( this ).attr( 'id' ).substring( 8 ); + var td = $( this ).parent().parent().siblings( '.pll-edit-column' ); + + $( this ).autocomplete( + { + minLength: 0, + source: ajaxurl + '?action=pll_terms_not_translated' + + '&term_language=' + $( '#term_lang_choice' ).val() + + '&term_id=' + $( "input[name='tag_ID']" ).val() + + '&taxonomy=' + $( "input[name='taxonomy']" ).val() + + '&translation_language=' + tr_lang + + '&post_type=' + typenow + + '&_pll_nonce=' + $( '#_pll_nonce' ).val(), + select: function ( event, ui ) { + $( '#htr_lang_' + tr_lang ).val( ui.item.id ); + // ui.item.link is built and come from server side and is well escaped when necessary + td.html( ui.item.link ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + }, + } + ); + + // when the input box is emptied + $( this ).on( + 'blur', + function () { + if ( ! $( this ).val() ) { + $( '#htr_lang_' + tr_lang ).val( 0 ); + // Value is retrieved from HTML already generated server side + td.html( td.siblings( '.hidden' ).children().clone() ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + } + ); + } + ); + } + + init_translations(); + + // ajax for changing the term's language + $( '#term_lang_choice' ).on( + 'change', + function () { + var value = $( this ).val(); + // The selected option in the dropdown list. + const selectedOption = event.target; + + var data = { + action: 'term_lang_choice', + lang: value, + from_tag: $( "input[name='from_tag']" ).val(), + term_id: $( "input[name='tag_ID']" ).val(), + taxonomy: $( "input[name='taxonomy']" ).val(), + post_type: typenow, + _pll_nonce: $( '#_pll_nonce' ).val() + }; + + $.post( + ajaxurl, + data, + function ( response ) { + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + switch ( this.what ) { + case 'translations': // translations fields + // Data is built and come from server side and is well escaped when necessary + $( "#term-translations" ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + init_translations(); + break; + case 'parent': // parent dropdown list for hierarchical taxonomies + // data correctly escaped in PLL_Admin_Filters_Term::term_lang_choice method which uses wp_dropdown_categories function. + $( '#parent' ).replaceWith( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + break; + case 'tag_cloud': // popular items + // data correctly escaped in PLL_Admin_Filters_Term::term_lang_choice method which uses wp_tag_cloud and wp_generate_tag_cloud functions. + $( '.tagcloud' ).replaceWith( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + break; + case 'flag': // flag in front of the select dropdown + // Data is built and come from server side and is well escaped when necessary + $( '.pll-select-flag' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + break; + } + } + ); + + // Creates an event once the language has been successfully changed. + const onTermLangChoice = new CustomEvent( + "onTermLangChoice", + { + detail: { + lang: JSON.parse( selectedOption.options[selectedOption.options.selectedIndex].getAttribute( 'data-lang' ) ) + }, + } + ); + document.dispatchEvent( onTermLangChoice ); + } + ); + } + ); + + // Listen to `onTermLangChoice` to perform actions after the language has been changed. + document.addEventListener( + 'onTermLangChoice', + ( e ) => { + // Modifies the text direction. + let dir = e.detail.lang.is_rtl ? 'rtl' : 'ltr' + $( 'body' ).removeClass( 'pll-dir-rtl' ).removeClass( 'pll-dir-ltr' ).addClass( 'pll-dir-' + dir ); + } + ); + } +); + diff --git a/wp-content/plugins/polylang/js/build/term.min.js b/wp-content/plugins/polylang/js/build/term.min.js new file mode 100644 index 0000000..2613b2b --- /dev/null +++ b/wp-content/plugins/polylang/js/build/term.min.js @@ -0,0 +1 @@ +jQuery(function(t){const e=t=>{for(const e of t){const t=Array.from(e.addedNodes).filter(t=>t.nodeType===Node.ELEMENT_NODE)[0];if(00){const a=t.querySelector('select[name="inline_lang_choice"]'),n=document.querySelector("#lang_"+String(e)).innerHTML;a.value=n;const l=document.querySelector(`#default_cat_${e}`)?.innerHTML;e==l&&(a.disabled=!0)}}}},a=document.getElementById("the-list");if(null!==a){const t={childList:!0,subtree:!0};new MutationObserver(e).observe(a,t)}}),jQuery(function(t){t(document).ajaxSuccess(function(e,a,n){function l(e){var a=new Array;t(".translation_"+e).each(function(){a.push(t(this).parent().parent().attr("id").substring(4))});var n={action:"pll_update_term_rows",term_id:e,translations:a.join(","),taxonomy:t("input[name='taxonomy']").val(),post_type:t("input[name='post_type']").val(),screen:t("input[name='screen']").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,n,function(e){if(e){var a=wpAjax.parseAjaxResponse(e,"pll-ajax-response");t.each(a.responses,function(){"row"==this.what&&t("#tag-"+this.supplemental.term_id).replaceWith(this.data)})}})}var s=wpAjax.unserialize(n.data);if(void 0!==s.action)switch(s.action){case"add-tag":res=wpAjax.parseAjaxResponse(a.responseXML,"pll-ajax-response"),t.each(res.responses,function(){"term"==this.what&&l(this.supplemental.term_id)}),t(".htr_lang").val(0);break;case"delete-tag":l(s.tag_ID);break;case"inline-save-tax":l(s.tax_ID)}})}),jQuery(function(t){function e(){t(".tr_lang").each(function(){var e=t(this).attr("id").substring(8),a=t(this).parent().parent().siblings(".pll-edit-column");t(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_terms_not_translated&term_language="+t("#term_lang_choice").val()+"&term_id="+t("input[name='tag_ID']").val()+"&taxonomy="+t("input[name='taxonomy']").val()+"&translation_language="+e+"&post_type="+typenow+"&_pll_nonce="+t("#_pll_nonce").val(),select:function(n,l){t("#htr_lang_"+e).val(l.item.id),a.html(l.item.link)}}),t(this).on("blur",function(){t(this).val()||(t("#htr_lang_"+e).val(0),a.html(a.siblings(".hidden").children().clone()))})})}e(),t("#term_lang_choice").on("change",function(){var a=t(this).val();const n=event.target;var l={action:"term_lang_choice",lang:a,from_tag:t("input[name='from_tag']").val(),term_id:t("input[name='tag_ID']").val(),taxonomy:t("input[name='taxonomy']").val(),post_type:typenow,_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,l,function(a){var l=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(l.responses,function(){switch(this.what){case"translations":t("#term-translations").html(this.data),e();break;case"parent":t("#parent").replaceWith(this.data);break;case"tag_cloud":t(".tagcloud").replaceWith(this.data);break;case"flag":t(".pll-select-flag").html(this.data)}});const s=new CustomEvent("onTermLangChoice",{detail:{lang:JSON.parse(n.options[n.options.selectedIndex].getAttribute("data-lang"))}});document.dispatchEvent(s)})}),document.addEventListener("onTermLangChoice",e=>{let a=e.detail.lang.is_rtl?"rtl":"ltr";t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+a)})}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/user.js b/wp-content/plugins/polylang/js/build/user.js new file mode 100644 index 0000000..03e6714 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/user.js @@ -0,0 +1,34 @@ +/** + * Adds one biography input field per language in the user profile. + * + * @package Polylang + */ + +jQuery( + function ( $ ) { + // biography + // FIXME there is probably a more efficient way to do this + var td = $( '#description' ).parent(); + var d = $( '#description' ).clone(); + var span = td.children( '.description' ).clone(); + td.children().remove(); + + $( '.biography' ).each( + function () { + lang = $( this ).attr( 'name' ).split( '___' ); + desc = d.clone(); + desc.attr( 'name', 'description_' + lang[0] ); + desc.attr( 'id', 'description_' + lang[0] ); + // Whitelist because description and lang value is already escaped by the side of PHP + desc.html( $( this ).val() ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + td.append( $( '
    ' ).text( lang[1] ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + td.append( desc ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + } + ); + + td.append( '
    ' ); + // Whitelist because description come from html code generated by WordPress + td.append( span ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + } +); + diff --git a/wp-content/plugins/polylang/js/build/user.min.js b/wp-content/plugins/polylang/js/build/user.min.js new file mode 100644 index 0000000..cfa30ef --- /dev/null +++ b/wp-content/plugins/polylang/js/build/user.min.js @@ -0,0 +1 @@ +jQuery(function(e){var n=e("#description").parent(),i=e("#description").clone(),t=n.children(".description").clone();n.children().remove(),e(".biography").each(function(){lang=e(this).attr("name").split("___"),desc=i.clone(),desc.attr("name","description_"+lang[0]),desc.attr("id","description_"+lang[0]),desc.html(e(this).val()),n.append(e("
    ").text(lang[1])),n.append(desc)}),n.append("
    "),n.append(t)}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/widgets.js b/wp-content/plugins/polylang/js/build/widgets.js new file mode 100644 index 0000000..3d2ec2c --- /dev/null +++ b/wp-content/plugins/polylang/js/build/widgets.js @@ -0,0 +1,152 @@ +/** + * Adds a flag to the widgets filtered by a language. + * + * @package Polylang + */ + +jQuery( + function ( $ ) { + var widgets_container, + widgets_selector, + flags, + isBlockEditor = 'undefined' !== typeof wp.blockEditor; + + if ( 'undefined' !== typeof pll_widgets && pll_widgets.hasOwnProperty( 'flags' ) ) { + flags = pll_widgets.flags; + } + + /** + * Prepend widget titles with a flag once a language is selected. + * + * @param {object} widget The widget element. + * @return {void} Nothing. + */ + function add_flag( widget ) { + if ( ! flags ) { + return; + } + widget = $( widget ); + var title = isBlockEditor ? widget.prev('h3') : $( '.widget-top .widget-title h3', widget ), + locale = $( '.pll-lang-choice option:selected', widget ).val(), + // Icon is HTML built and come from server side and is well escaped when necessary + icon = ( locale && flags.hasOwnProperty( locale ) ) ? flags[ locale ] : null; + + if ( icon ) { + icon += '   '; + var current = $( '.pll-lang', title ); + if ( current.length ) { + current.html( icon ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } else { + flag = $( '' ).addClass( 'pll-lang' ).html( icon ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // See the comment above about the icon which is safe. So it is also safe to prepend flag which uses icon. + title.prepend( flag ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + } else { + $( '.pll-lang', title ).remove(); + } + } + + if ( isBlockEditor ) { + + widgets_container = $( '.edit-widgets-main-block-list' ); + widgets_selector = '.widget'; + + // Update flags when we click on the legacy widget to display its form. + widgets_container.on( + 'click', + '.wp-block-legacy-widget', + function () { + add_flag( $( this ).find( '.widget' ) ); + } + ); + + } else { + if ( 'undefined' !== typeof wp.customize ) { + + widgets_container = $( '#customize-controls' ); + widgets_selector = '.customize-control .widget'; + + /** + * WP Customizer add control listener. + * + * @link https://wordpress.stackexchange.com/questions/256536/callback-after-wordpress-customizer-complete-loading + * + * @param {object} control The control type. + * @return {void} Nothing. + */ + function customize_add_flag( control ) { + if ( ! control.extended( wp.customize.Widgets.WidgetControl ) ) { + return; + } + + /* + * Make sure the widget's contents are embedded; normally this is done + * when the control is expanded, for DOM performance reasons. + */ + control.embedWidgetContent(); + + // Now we know for sure the widget is fully embedded. + add_flag( control.container.find( '.widget' ) ); + } + wp.customize.control.each( customize_add_flag ); + wp.customize.control.bind( 'add', customize_add_flag ); + + } else { + + widgets_container = $( '#widgets-right' ); + widgets_selector = '.widget'; + + } + + // Add flags on load. + $( widgets_selector, widgets_container ).each( + function () { + add_flag( this ); + } + ); + } + + // Update flags. + widgets_container.on( + 'change', + '.pll-lang-choice', + function () { + add_flag( $( this ).parents( '.widget' ) ); + } + ); + + function pll_toggle( a, test ) { + test ? a.show() : a.hide(); + } + + // Remove all options if dropdown is checked. + $( '.widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list' ).on( + 'change', + '.pll-dropdown', + function () { + var this_id = $( this ).parent().parent().parent().children( '.widget-id' ).attr( 'value' ); + pll_toggle( $( '.no-dropdown-' + this_id ), true != $( this ).prop( 'checked' ) ); + } + ); + + // Disallow unchecking both show names and show flags. + var options = ['-show_flags', '-show_names']; + $.each( + options, + function ( i, v ) { + $( '.widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list' ).on( + 'change', + '.pll' + v, + function () { + var this_id = $( this ).parent().parent().parent().children( '.widget-id' ).attr( 'value' ); + if ( true != $( this ).prop( 'checked' ) ) { + $( '#widget-' + this_id + options[ 1 - i ] ).prop( 'checked', true ); + } + } + ); + } + ); + + } +); + diff --git a/wp-content/plugins/polylang/js/build/widgets.min.js b/wp-content/plugins/polylang/js/build/widgets.min.js new file mode 100644 index 0000000..2a22a67 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/widgets.min.js @@ -0,0 +1 @@ +jQuery(function(e){var t,i,n,o=void 0!==wp.blockEditor;function l(t){if(n){t=e(t);var i=o?t.prev("h3"):e(".widget-top .widget-title h3",t),l=e(".pll-lang-choice option:selected",t).val(),d=l&&n.hasOwnProperty(l)?n[l]:null;if(d){d+="   ";var s=e(".pll-lang",i);s.length?s.html(d):(flag=e("").addClass("pll-lang").html(d),i.prepend(flag))}else e(".pll-lang",i).remove()}}if("undefined"!=typeof pll_widgets&&pll_widgets.hasOwnProperty("flags")&&(n=pll_widgets.flags),o)i=".widget",(t=e(".edit-widgets-main-block-list")).on("click",".wp-block-legacy-widget",function(){l(e(this).find(".widget"))});else{if(void 0!==wp.customize){function d(e){e.extended(wp.customize.Widgets.WidgetControl)&&(e.embedWidgetContent(),l(e.container.find(".widget")))}t=e("#customize-controls"),i=".customize-control .widget",wp.customize.control.each(d),wp.customize.control.bind("add",d)}else t=e("#widgets-right"),i=".widget";e(i,t).each(function(){l(this)})}t.on("change",".pll-lang-choice",function(){l(e(this).parents(".widget"))}),e(".widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list").on("change",".pll-dropdown",function(){var t,i=e(this).parent().parent().parent().children(".widget-id").attr("value");t=e(".no-dropdown-"+i),1!=e(this).prop("checked")?t.show():t.hide()});var s=["-show_flags","-show_names"];e.each(s,function(t,i){e(".widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list").on("change",".pll"+i,function(){var i=e(this).parent().parent().parent().children(".widget-id").attr("value");1!=e(this).prop("checked")&&e("#widget-"+i+s[1-t]).prop("checked",!0)})})}); \ No newline at end of file diff --git a/wp-content/plugins/polylang/modules/REST/API.php b/wp-content/plugins/polylang/modules/REST/API.php new file mode 100644 index 0000000..7af7e38 --- /dev/null +++ b/wp-content/plugins/polylang/modules/REST/API.php @@ -0,0 +1,62 @@ +model = $model; + } + + /** + * Adds hooks and registers endpoints. + * + * @since 3.7 + * + * @return void + */ + public function init(): void { + $this->languages = new V1\Languages( $this->model ); + $this->languages->register_routes(); + + $this->settings = new V1\Settings( $this->model ); + $this->settings->register_routes(); + } +} diff --git a/wp-content/plugins/polylang/modules/REST/Abstract_Controller.php b/wp-content/plugins/polylang/modules/REST/Abstract_Controller.php new file mode 100644 index 0000000..ca85dd4 --- /dev/null +++ b/wp-content/plugins/polylang/modules/REST/Abstract_Controller.php @@ -0,0 +1,32 @@ +add_data( array( 'status' => $status_code ) ); + return $error; + } +} diff --git a/wp-content/plugins/polylang/modules/REST/V1/Languages.php b/wp-content/plugins/polylang/modules/REST/V1/Languages.php new file mode 100644 index 0000000..119a04f --- /dev/null +++ b/wp-content/plugins/polylang/modules/REST/V1/Languages.php @@ -0,0 +1,792 @@ +namespace = 'pll/v1'; + $this->rest_base = 'languages'; + $this->languages = $model->languages; + $this->translatable_objects = $model->translatable_objects; + } + + /** + * Registers the routes for languages. + * + * @since 3.7 + * + * @return void + */ + public function register_routes(): void { + register_rest_route( + $this->namespace, + "/{$this->rest_base}", + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + + $readable = array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ); + + register_rest_route( + $this->namespace, + "/{$this->rest_base}/(?P[\d]+)", + array( + 'args' => array( + 'term_id' => array( + 'description' => __( 'Unique identifier for the language.', 'polylang' ), + 'type' => 'integer', + ), + ), + $readable, + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + register_rest_route( + $this->namespace, + sprintf( '/%1$s/(?P%2$s)', $this->rest_base, Languages_Model::INNER_SLUG_PATTERN ), + array( + 'args' => array( + 'slug' => array( + 'description' => __( 'Language code - preferably 2-letters ISO 639-1 (for example: en).', 'polylang' ), + 'type' => 'string', + ), + ), + $readable, + 'schema' => array( $this, 'get_public_item_schema' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + } + + /** + * Retrieves all languages. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function get_items( $request ) { + $response = array(); + + foreach ( $this->languages->get_list() as $language ) { + $language = $this->prepare_item_for_response( $language, $request ); + $response[] = $this->prepare_response_for_collection( $language ); + } + + return rest_ensure_response( $response ); + } + + /** + * Creates one language from the collection. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function create_item( $request ) { + if ( isset( $request['term_id'] ) ) { + return new WP_Error( + 'rest_exists', + __( 'Cannot create existing language.', 'polylang' ), + array( 'status' => 400 ) + ); + } + + // At this point, `$request['locale']` is provided (but maybe not valid). + $prepared = $this->prepare_item_for_database( $request ); + + if ( is_wp_error( $prepared ) ) { + return $prepared; + } + + /** + * @phpstan-var array{ + * locale: non-empty-string, + * slug: non-empty-string, + * name: non-empty-string, + * rtl: bool, + * term_group: int, + * flag: non-empty-string, + * no_default_cat: bool + * } $args + */ + $args = (array) $prepared; + $result = $this->languages->add( $args ); + + if ( is_wp_error( $result ) ) { + return $this->add_status_to_error( $result ); + } + + /** @var PLL_Language */ + $language = $this->languages->get( $args['locale'] ); + return $this->prepare_item_for_response( $language, $request ); + } + + /** + * Retrieves one language from the collection. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function get_item( $request ) { + $language = $this->get_language( $request ); + + if ( is_wp_error( $language ) ) { + return $language; + } + + return $this->prepare_item_for_response( $language, $request ); + } + + /** + * Updates one language from the collection. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function update_item( $request ) { + $prepared = $this->prepare_item_for_database( $request ); + + if ( is_wp_error( $prepared ) ) { + // Should not happen, but if it does, it's our fault. + return $prepared; + } + + /** + * @phpstan-var array{ + * lang_id: int, + * locale: non-empty-string, + * slug: non-empty-string, + * name: non-empty-string, + * rtl: bool, + * term_group: int, + * flag?: non-empty-string + * } $args + */ + $args = (array) $prepared; + $update = $this->languages->update( $args ); + + if ( is_wp_error( $update ) ) { + return $this->add_status_to_error( $update ); + } + + /** @var PLL_Language */ + $language = $this->languages->get( $args['lang_id'] ); + return $this->prepare_item_for_response( $language, $request ); + } + + /** + * Deletes one language from the collection. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function delete_item( $request ) { + $language = $this->get_language( $request ); + + if ( is_wp_error( $language ) ) { + return $language; + } + + $this->languages->delete( $language->term_id ); + + $previous = $this->prepare_item_for_response( $language, $request ); + $response = new WP_REST_Response(); + $response->set_data( + array( + 'deleted' => true, + 'previous' => $previous->get_data(), + ) + ); + + return $response; + } + + /** + * Checks if a given request has access to get the languages. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function get_items_permissions_check( $request ) { + if ( 'edit' === $request['context'] && ! $this->check_update_permission() ) { + return new WP_Error( + 'rest_forbidden_context', + __( 'Sorry, you are not allowed to edit languages.', 'polylang' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + return true; + } + + /** + * Checks if a given request has access to create a language. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to create languages, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( ! $this->check_update_permission() ) { + return new WP_Error( + 'rest_cannot_create', + __( 'Sorry, you are not allowed to create a language.', 'polylang' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + return true; + } + + /** + * Checks if a given request has access to get a specific language. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access for the language, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function get_item_permissions_check( $request ) { + return $this->get_items_permissions_check( $request ); + } + + /** + * Checks if a given request has access to update a specific language. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to update the language, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function update_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( ! $this->check_update_permission() ) { + return new WP_Error( + 'rest_cannot_update', + __( 'Sorry, you are not allowed to edit this language.', 'polylang' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + return true; + } + + /** + * Checks if a given request has access to delete a specific language. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to delete the language, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function delete_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( ! $this->check_update_permission() ) { + return new WP_Error( + 'rest_cannot_delete', + __( 'Sorry, you are not allowed to delete this language.', 'polylang' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + return true; + } + + /** + * Prepares the language for the REST response. + * + * @since 3.7 + * + * @param PLL_Language $item Language object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function prepare_item_for_response( $item, $request ) { + $data = $item->to_array(); + $fields = $this->get_fields_for_response( $request ); + $response = array(); + + $data['is_rtl'] = (bool) $data['is_rtl']; + $data['host'] = (string) $data['host']; + + foreach ( $data as $language_prop => $prop_value ) { + if ( rest_is_field_included( $language_prop, $fields ) ) { + $response[ $language_prop ] = $prop_value; + } + } + + /** @var WP_REST_Response */ + return rest_ensure_response( $response ); + } + + /** + * Retrieves the language's schema, conforming to JSON Schema. + * + * @since 3.7 + * + * @return array Item schema data. + */ + public function get_item_schema(): array { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $this->schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'language', + 'type' => 'object', + 'properties' => array( + 'term_id' => array( + 'description' => __( 'Unique identifier for the language.', 'polylang' ), + 'type' => 'integer', + 'minimum' => 1, + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'The name is how it is displayed on your site (for example: English).', 'polylang' ), + 'type' => 'string', + 'minLength' => 1, + 'context' => array( 'view', 'edit' ), + ), + 'slug' => array( + 'description' => __( 'Language code - preferably 2-letters ISO 639-1 (for example: en).', 'polylang' ), + 'type' => 'string', + 'pattern' => Languages_Model::SLUG_PATTERN, + 'context' => array( 'view', 'edit' ), + ), + 'locale' => array( + 'description' => __( 'WordPress Locale for the language (for example: en_US).', 'polylang' ), + 'type' => 'string', + 'pattern' => Languages_Model::LOCALE_PATTERN, + 'context' => array( 'view', 'edit' ), + 'required' => true, + ), + 'w3c' => array( + 'description' => __( 'W3C Locale for the language (for example: en-US).', 'polylang' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'facebook' => array( + 'description' => __( 'Facebook Locale for the language (for example: en_US).', 'polylang' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'is_rtl' => array( + 'description' => sprintf( + /* translators: %s is a value. */ + __( 'Text direction. %s for right-to-left.', 'polylang' ), + '`true`' + ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + ), + 'term_group' => array( + 'description' => __( 'Position of the language in the language switcher.', 'polylang' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + 'flag_code' => array( + 'description' => __( 'Flag code corresponding to ISO 3166-1 (for example: us for the United States flag).', 'polylang' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'flag_url' => array( + 'description' => __( 'Flag URL.', 'polylang' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'flag' => array( + 'description' => __( 'HTML tag for the flag.', 'polylang' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'custom_flag_url' => array( + 'description' => __( 'Custom flag URL.', 'polylang' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'custom_flag' => array( + 'description' => __( 'HTML tag for the custom flag.', 'polylang' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'is_default' => array( + 'description' => __( 'Tells whether the language is the default one.', 'polylang' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'active' => array( + 'description' => __( 'Tells whether the language is active.', 'polylang' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'home_url' => array( + 'description' => __( 'Home URL in this language.', 'polylang' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'search_url' => array( + 'description' => __( 'Search URL in this language.', 'polylang' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'host' => array( + 'description' => __( 'Host for this language.', 'polylang' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'page_on_front' => array( + 'description' => __( 'Page on front ID in this language.', 'polylang' ), + 'type' => 'integer', + 'minimum' => 0, + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'page_for_posts' => array( + 'description' => __( 'Identifier of the page for posts in this language.', 'polylang' ), + 'type' => 'integer', + 'minimum' => 0, + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'fallbacks' => array( + 'description' => __( 'List of language locale fallbacks.', 'polylang' ), + 'type' => 'array', + 'uniqueItems' => true, + 'items' => array( + 'type' => 'string', + 'pattern' => Languages_Model::LOCALE_PATTERN, + ), + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'term_props' => array( + 'description' => __( 'Language properties.', 'polylang' ), + 'type' => 'object', + 'properties' => array(), + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'no_default_cat' => array( + 'description' => __( 'Tells whether the default category must be created when creating a new language.', 'polylang' ), + 'type' => 'boolean', + 'context' => array( 'edit' ), + 'default' => false, + ), + ), + ); + + foreach ( $this->translatable_objects as $translatable_object ) { + $this->schema['properties']['term_props']['properties'][ $translatable_object->get_tax_language() ] = array( + 'description' => $translatable_object->get_rest_description(), + 'type' => 'object', + 'properties' => array( + 'term_id' => array( + /* translators: %s is the name of the term property (`term_id` or `term_taxonomy_id`). */ + 'description' => sprintf( __( 'The %s of the language term for this translatable entity.', 'polylang' ), '`term_id`' ), + 'type' => 'integer', + 'minimum' => 1, + ), + 'term_taxonomy_id' => array( + /* translators: %s is the name of the term property (`term_id` or `term_taxonomy_id`). */ + 'description' => sprintf( __( 'The %s of the language term for this translatable entity.', 'polylang' ), '`term_taxonomy_id`' ), + 'type' => 'integer', + 'minimum' => 1, + ), + 'count' => array( + 'description' => __( 'Number of items of this type of content in this language.', 'polylang' ), + 'type' => 'integer', + 'minimum' => 0, + ), + ), + ); + } + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves an array of endpoint arguments from the item schema for the controller. + * Ensures that the `no_default_cat` property is returned only for `CREATABLE` requests. + * + * @since 3.7 + * + * @param string $method Optional. HTTP method of the request. Default WP_REST_Server::CREATABLE. + * @return array Endpoint arguments. + */ + public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { + $schema = $this->get_item_schema(); + if ( WP_REST_Server::CREATABLE !== $method ) { + unset( $schema['properties']['no_default_cat'] ); + } + return rest_get_endpoint_args_for_schema( $schema, $method ); + } + + /** + * Prepares one language for create or update operation. + * + * @since 3.7 + * + * @param WP_REST_Request $request Request object. + * @return object|WP_Error The prepared language, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + protected function prepare_item_for_database( $request ) { + if ( isset( $request['term_id'] ) ) { + // Update a language. + $language = $this->get_language( $request ); + + if ( is_wp_error( $language ) ) { + return $language; + } + + return (object) array( + 'lang_id' => $language->term_id, + 'name' => $request['name'] ?? $language->name, + 'slug' => $request['slug'] ?? $language->slug, + 'locale' => $request['locale'] ?? $language->locale, + 'rtl' => $request['is_rtl'] ?? (bool) $language->is_rtl, + 'flag' => $request['flag_code'] ?? $language->flag_code, + 'term_group' => $request['term_group'] ?? $language->term_group, + ); + } + + // Create a language. + if ( empty( $request['locale'] ) ) { + // Should not happen. + return new WP_Error( + 'rest_invalid_locale', + __( 'The locale is invalid.', 'polylang' ), + array( 'status' => 400 ) + ); + } + + if ( isset( $request['name'], $request['slug'], $request['is_rtl'], $request['flag_code'] ) ) { + return (object) array( + 'name' => $request['name'], + 'slug' => $request['slug'], + 'locale' => $request['locale'], + 'rtl' => $request['is_rtl'], + 'flag' => $request['flag_code'], + 'term_group' => $request['term_group'] ?? 0, + 'no_default_cat' => $request['no_default_cat'] ?? false, + ); + } + + // Create a language from our default list with only the locale. + $languages = include POLYLANG_DIR . '/settings/languages.php'; + + if ( empty( $languages[ $request['locale'] ] ) ) { + return new WP_Error( + 'pll_rest_invalid_locale', + __( 'The locale is invalid.', 'polylang' ), + array( 'status' => 400 ) + ); + } + + $language = (object) $languages[ $request['locale'] ]; + + return (object) array( + 'name' => $request['name'] ?? $language->name, + 'slug' => $request['slug'] ?? $language->code, + 'locale' => $request['locale'], + 'rtl' => $request['is_rtl'] ?? 'rtl' === $language->dir, + 'flag' => $request['flag_code'] ?? $language->flag, + 'term_group' => $request['term_group'] ?? 0, + 'no_default_cat' => $request['no_default_cat'] ?? false, + ); + } + + /** + * Tells if languages can be edited. + * + * @since 3.7 + * + * @return bool + */ + protected function check_update_permission(): bool { + return current_user_can( 'manage_options' ); + } + + /** + * Returns the language, if the ID is valid. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return PLL_Language|WP_Error Language object if the ID or slug is valid, WP_Error otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + private function get_language( WP_REST_Request $request ) { + if ( isset( $request['term_id'] ) ) { + $error = new WP_Error( + 'rest_invalid_id', + __( 'Invalid language ID', 'polylang' ), + array( 'status' => 404 ) + ); + + if ( $request['term_id'] <= 0 ) { + return $error; + } + + $language = $this->languages->get( (int) $request['term_id'] ); + + if ( ! $language instanceof PLL_Language ) { + return $error; + } + + return $language; + } + + if ( isset( $request['slug'] ) ) { + $language = $this->languages->get( (string) $request['slug'] ); + + if ( ! $language instanceof PLL_Language ) { + return new WP_Error( + 'rest_invalid_slug', + __( 'Invalid language slug', 'polylang' ), + array( 'status' => 404 ) + ); + } + + return $language; + } + + // Should not happen. + return new WP_Error( + 'rest_invalid_identifier', + __( 'Invalid language identifier', 'polylang' ), + array( 'status' => 404 ) + ); + } +} diff --git a/wp-content/plugins/polylang/modules/REST/V1/Settings.php b/wp-content/plugins/polylang/modules/REST/V1/Settings.php new file mode 100644 index 0000000..593814d --- /dev/null +++ b/wp-content/plugins/polylang/modules/REST/V1/Settings.php @@ -0,0 +1,205 @@ +namespace = 'pll/v1'; + $this->rest_base = 'settings'; + $this->options = $model->options; + $this->languages = $model->languages; + } + + /** + * Registers the routes for options. + * + * @since 3.7 + * + * @return void + */ + public function register_routes(): void { + register_rest_route( + $this->namespace, + "/{$this->rest_base}", + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + } + + /** + * Retrieves all options. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function get_item( $request ) { + return $this->prepare_item_for_response( $this->options->get_all(), $request ); + } + + /** + * Updates option(s). + * This allows to update one or several options. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function update_item( $request ) { + $errors = new WP_Error(); + $schema = $this->options->get_schema(); + $options = array_intersect_key( + $request->get_params(), + rest_get_endpoint_args_for_schema( $schema, WP_REST_Server::EDITABLE ) // Remove fields with `readonly`. + ); + + foreach ( $options as $option_name => $new_value ) { + $previous_value = $this->options->get( $option_name ); + + if ( 'default_lang' === $option_name ) { + $result = $this->languages->update_default( $new_value ); + } else { + $result = $this->options->set( $option_name, $new_value ); + } + + if ( $result->has_errors() ) { + $errors->merge_from( $result ); + continue; + } + + if ( $this->options->get( $option_name ) === $previous_value ) { + continue; + } + + switch ( $option_name ) { + case 'rewrite': + case 'force_lang': + case 'hide_default': + flush_rewrite_rules(); + } + } + + if ( $errors->has_errors() ) { + return $this->add_status_to_error( $errors ); + } + + return $this->prepare_item_for_response( $this->options->get_all(), $request ); + } + + /** + * Checks if a given request has access to update the options. + * + * @since 3.7 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to update the option, WP_Error object otherwise. + * + * @phpstan-template T of array + * @phpstan-param WP_REST_Request $request + */ + public function update_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( ! current_user_can( 'manage_options' ) ) { + return new WP_Error( + 'rest_forbidden_context', + __( 'Sorry, you are not allowed to edit options.', 'polylang' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + return true; + } + + /** + * Prepares the option value for the REST response. + * + * @since 3.7 + * + * @param array $item Option values. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + * + * @phpstan-template T of array + * @phpstan-param array $item + * @phpstan-param WP_REST_Request $request + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $response = array(); + + foreach ( $item as $option => $value ) { + if ( rest_is_field_included( $option, $fields ) ) { + $response[ $option ] = $value; + } + } + + /** @var WP_REST_Response */ + return rest_ensure_response( $response ); + } + + /** + * Retrieves the options' schema, conforming to JSON Schema. + * + * @since 3.7 + * + * @return array Item schema data. + */ + public function get_item_schema(): array { + return $this->add_additional_fields_schema( $this->options->get_schema() ); + } +} diff --git a/wp-content/plugins/polylang/modules/REST/load.php b/wp-content/plugins/polylang/modules/REST/load.php new file mode 100644 index 0000000..b2af91b --- /dev/null +++ b/wp-content/plugins/polylang/modules/REST/load.php @@ -0,0 +1,16 @@ +rest = new API( $polylang->model ); + add_action( 'rest_api_init', array( $polylang->rest, 'init' ) ); + } +); diff --git a/wp-content/plugins/polylang/modules/machine-translation/load.php b/wp-content/plugins/polylang/modules/machine-translation/load.php new file mode 100644 index 0000000..ea9e04f --- /dev/null +++ b/wp-content/plugins/polylang/modules/machine-translation/load.php @@ -0,0 +1,18 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Machine_Translation'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php b/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php new file mode 100644 index 0000000..9651433 --- /dev/null +++ b/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php @@ -0,0 +1,46 @@ + 'machine_translation', + 'title' => __( 'Machine Translation', 'polylang' ), + 'description' => __( 'Allows linkage to DeepL Translate.', 'polylang' ), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } +} diff --git a/wp-content/plugins/polylang/modules/share-slug/load.php b/wp-content/plugins/polylang/modules/share-slug/load.php new file mode 100644 index 0000000..771939c --- /dev/null +++ b/wp-content/plugins/polylang/modules/share-slug/load.php @@ -0,0 +1,20 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Share_Slug'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php b/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php new file mode 100644 index 0000000..17f6913 --- /dev/null +++ b/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php @@ -0,0 +1,56 @@ + 'share-slugs', + 'title' => __( 'Share slugs', 'polylang' ), + 'description' => $this->get_description(), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } + + /** + * Returns the module description. + * + * @since 3.1 + * + * @return string + */ + protected function get_description() { + return __( 'Allows to share the same URL slug across languages for posts and terms.', 'polylang' ); + } +} diff --git a/wp-content/plugins/polylang/modules/site-health/admin-site-health.php b/wp-content/plugins/polylang/modules/site-health/admin-site-health.php new file mode 100644 index 0000000..7b00a9a --- /dev/null +++ b/wp-content/plugins/polylang/modules/site-health/admin-site-health.php @@ -0,0 +1,528 @@ +model = &$polylang->model; + $this->static_pages = &$polylang->static_pages; + + // Information tab. + add_filter( 'debug_information', array( $this, 'info_options' ), 15 ); + add_filter( 'debug_information', array( $this, 'info_languages' ), 15 ); + add_filter( 'debug_information', array( $this, 'info' ), 15 ); + + // Tests Tab. + add_filter( 'site_status_tests', array( $this, 'status_tests' ) ); + add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) ); // Require simplexml in Site health. + } + + /** + * Returns a list of keys to exclude from the site health information. + * + * @since 2.8 + * + * @return string[] List of option keys to ignore. + */ + protected function exclude_options_keys() { + return array( + 'uninstall', + 'first_activation', + ); + } + + /** + * Returns a list of keys to exclude from the site health information. + * + * @since 2.8 + * + * @return string[] List of language keys to ignore. + */ + protected function exclude_language_keys() { + return array( + 'flag', + 'host', + 'taxonomy', + 'description', + 'parent', + 'filter', + 'custom_flag', + ); + } + + /** + * Formats an array to display in options information. + * + * @since 2.8 + * + * @param array $array An array of formatted data. + * @return string + */ + protected function format_array( $array ) { + array_walk( + $array, + function ( &$value, $key ) { + if ( is_array( $value ) ) { + $ids = implode( ' , ', $value ); + $value = "$key => $ids"; + } else { + $value = "$key => $value"; + } + } + ); + return implode( ' | ', $array ); + } + + /** + * Transforms the option value to readable human sentence. + * + * @since 3.3 + * + * @param string $key Option name. + * @param mixed $value Option value. + * @return mixed Option value. + */ + public function format_value( $key, $value ) { + switch ( $key ) { + case 'browser': + if ( ! $value ) { + $value = '0: ' . __( 'Detect browser language deactivated', 'polylang' ); + break; + } + $value = '1: ' . __( 'Detect browser language activated', 'polylang' ); + break; + case 'rewrite': + if ( $value ) { + $value = '1: ' . sprintf( + /* translators: %s is a URL slug: `/language/`. */ + __( 'Remove %s in pretty permalinks', 'polylang' ), + '`/language/`' + ); + break; + } + $value = '0: ' . sprintf( + /* translators: %s is a URL slug: `/language/`. */ + __( 'Keep %s in pretty permalinks', 'polylang' ), + '`/language/`' + ); + break; + case 'hide_default': + if ( $value ) { + $value = '1: ' . __( 'Hide URL language information for default language', 'polylang' ); + break; + } + $value = '0: ' . __( 'Display URL language information for default language', 'polylang' ); + break; + case 'force_lang': + switch ( $value ) { + case '0': + $value = '0: ' . __( 'The language is set from content', 'polylang' ); + break; + case '1': + $value = '1: ' . __( 'The language is set from the directory name in pretty permalinks', 'polylang' ); + break; + case '2': + $value = '2: ' . __( 'The language is set from the subdomain name in pretty permalinks', 'polylang' ); + break; + case '3': + $value = '3: ' . __( 'The language is set from different domains', 'polylang' ); + break; + } + break; + case 'redirect_lang': + if ( $value ) { + $value = '1: ' . __( 'The front page URL contains the language code instead of the page name or page id', 'polylang' ); + break; + } + $value = '0: ' . __( 'The front page URL contains the page name or page id instead of the language code', 'polylang' ); + + break; + case 'media_support': + if ( ! $value ) { + $value = '0: ' . __( 'The media are not translated', 'polylang' ); + break; + } + $value = '1: ' . __( 'The media are translated', 'polylang' ); + break; + + case 'sync': + if ( empty( $value ) ) { + $value = '0: ' . __( 'Synchronization disabled', 'polylang' ); + } + break; + } + + return $value; + } + + /** + * Add Polylang Options to Site Health Information tab. + * + * @since 2.8 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info_options( $debug_info ) { + $fields = array(); + + foreach ( $this->model->options as $key => $value ) { + if ( in_array( $key, $this->exclude_options_keys() ) ) { + continue; + } + + $value = $this->format_value( $key, $value ); + + switch ( $key ) { + case 'domains': + if ( 3 === $this->model->options['force_lang'] ) { + $value = is_array( $value ) ? $value : array(); + $value = $this->format_array( $value ); + + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = $value; + } + break; + + case 'nav_menus': + $current_theme = get_stylesheet(); + if ( is_array( $value ) && isset( $value[ $current_theme ] ) ) { + foreach ( $value[ $current_theme ] as $location => $lang ) { + $lang = is_array( $lang ) ? $lang : array(); + + $fields[ $location ]['label'] = sprintf( 'menu: %s', $location ); + $fields[ $location ]['value'] = $this->format_array( $lang ); + } + } + break; + + case 'media': + $value = is_array( $value ) ? $value : array(); + foreach ( $value as $sub_key => $sub_value ) { + $fields[ "$key-$sub_key" ]['label'] = "$key $sub_key"; + $fields[ "$key-$sub_key" ]['value'] = $sub_value; + } + break; + + case 'post_types': + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_post_types() ); + break; + + case 'taxonomies': + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_taxonomies() ); + break; + + default: + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = empty( $value ) ? '0' : $value; + break; + } + } + + $debug_info['pll_options'] = array( + /* translators: placeholder is the plugin name */ + 'label' => sprintf( __( '%s options', 'polylang' ), POLYLANG ), + 'fields' => $fields, + ); + + return $debug_info; + } + + /** + * Adds Polylang Languages settings to Site Health Information tab. + * + * @since 2.8 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info_languages( $debug_info ) { + foreach ( $this->model->get_languages_list() as $language ) { + $fields = array(); + + foreach ( $language->to_array() as $key => $value ) { + if ( in_array( $key, $this->exclude_language_keys(), true ) ) { + continue; + } + + if ( empty( $value ) ) { + $value = '0'; + } + + $fields[ $key ]['label'] = $key; + + if ( 'term_props' === $key && is_array( $value ) ) { + $fields[ $key ]['value'] = $this->get_info_term_props( $value ); + } else { + $fields[ $key ]['value'] = $value; + } + + if ( 'term_group' === $key ) { + $fields[ $key ]['label'] = 'order'; // Changed for readability but not translated as other keys are not. + } + } + + $debug_info[ 'pll_language_' . $language->slug ] = array( + /* translators: %1$s placeholder is the language name, %2$s is the language code */ + 'label' => sprintf( __( 'Language: %1$s - %2$s', 'polylang' ), $language->name, $language->slug ), + /* translators: placeholder is the flag image */ + 'description' => sprintf( esc_html__( 'Flag used in the language switcher: %s', 'polylang' ), $this->get_flag( $language ) ), + 'fields' => $fields, + ); + } + + return $debug_info; + } + + /** + * Adds term props data to the info languages array. + * + * @since 3.4 + * + * @param array $value The term props data. + * @return array The term props data formatted for the info languages tab. + */ + protected function get_info_term_props( $value ) { + $return_value = array(); + + foreach ( $value as $language_taxonomy => $item ) { + $language_taxonomy_array = array_fill( 0, count( $item ), $language_taxonomy ); + + $keys_with_language_taxonomy = array_map( + function ( $key, $language_taxonomy ) { + return "{$language_taxonomy}/{$key}"; + }, + array_keys( $item ), + $language_taxonomy_array + ); + + $value = array_combine( $keys_with_language_taxonomy, $item ); + if ( is_array( $value ) ) { + $return_value = array_merge( $return_value, $value ); + } + } + return $return_value; + } + + /** + * Returns the flag used in the language switcher. + * + * @since 2.8 + * + * @param PLL_Language $language Language object. + * @return string + */ + protected function get_flag( $language ) { + $flag = $language->get_display_flag(); + return empty( $flag ) ? '' . esc_html__( 'Undefined', 'polylang' ) . '' : $flag; + } + + /** + * Add a Site Health test on homepage translation. + * + * @since 2.8 + * + * @param array $tests Array with tests declaration data. + * @return array + */ + public function status_tests( $tests ) { + // Add the test only if the homepage displays static page. + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $tests['direct']['pll_homepage'] = array( + 'label' => esc_html__( 'Homepage translated', 'polylang' ), + 'test' => array( $this, 'homepage_test' ), + ); + } + return $tests; + } + + /** + * Test if the home page is translated or not. + * + * @since 2.8 + * + * @return array $result Array with test results. + */ + public function homepage_test() { + $result = array( + 'label' => __( 'All languages have a translated homepage', 'polylang' ), + 'status' => 'good', + 'badge' => array( + 'label' => POLYLANG, + 'color' => 'blue', + ), + 'description' => sprintf( + '

    %s

    ', + esc_html__( 'It is mandatory to translate the static front page in all languages.', 'polylang' ) + ), + 'actions' => '', + 'test' => 'pll_homepage', + ); + + $message = $this->static_pages->get_must_translate_message(); + + if ( ! empty( $message ) ) { + $result['status'] = 'critical'; + $result['label'] = __( 'The homepage is not translated in all languages', 'polylang' ); + $result['description'] = sprintf( '

    %s

    ', $message ); + } + return $result; + } + + /** + * Add Polylang Warnings to Site Health Information tab. + * + * @since 3.1 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info( $debug_info ) { + $fields = array(); + + // Add Post Types without languages. + $posts_no_lang = $this->get_post_ids_without_lang(); + + if ( ! empty( $posts_no_lang ) ) { + $fields['post-no-lang']['label'] = __( 'Posts without language', 'polylang' ); + $fields['post-no-lang']['value'] = $this->format_array( $posts_no_lang ); + } + + $terms_no_lang = $this->get_term_ids_without_lang(); + + if ( ! empty( $terms_no_lang ) ) { + $fields['term-no-lang']['label'] = __( 'Terms without language', 'polylang' ); + $fields['term-no-lang']['value'] = $this->format_array( $terms_no_lang ); + } + + // Add WPML files. + $wpml_files = PLL_WPML_Config::instance()->get_files(); + if ( ! empty( $wpml_files ) ) { + $fields['wpml']['label'] = 'wpml-config.xml files'; + $fields['wpml']['value'] = $wpml_files; + + if ( ! extension_loaded( 'simplexml' ) ) { + $fields['simplexml']['label'] = __( 'PHP SimpleXML extension', 'polylang' ); + $fields['simplexml']['value'] = __( 'Not loaded. Contact your host provider.', 'polylang' ); + } + } + + // Create the section. + if ( ! empty( $fields ) ) { + $debug_info['pll_warnings'] = array( + /* translators: placeholder is the plugin name */ + 'label' => sprintf( __( '%s information', 'polylang' ), POLYLANG ), + 'fields' => $fields, + ); + } + + return $debug_info; + } + + /** + * Get an array with post_type as key and post ids as value. + * + * @since 3.1 + * + * @param int $limit Max number of posts to show per post type. `-1` to return all of them. Default is 5. + * @return int[][] Array containing an array of post ids. + * + * @phpstan-param -1|positive-int $limit + */ + public function get_post_ids_without_lang( $limit = 5 ) { + $posts = array(); + + foreach ( $this->model->get_translated_post_types() as $post_type ) { + $post_ids_with_no_language = $this->model->get_posts_with_no_lang( $post_type, $limit ); + + if ( ! empty( $post_ids_with_no_language ) ) { + foreach ( $post_ids_with_no_language as $id ) { + $posts[ $post_type ][] = $id; + } + } + } + + return $posts; + } + + /** + * Get an array with taxonomy as key and term ids as value. + * + * @since 3.1 + * + * @param int $limit Max number of terms to show per post type. `-1` to return all of them. Default is 5. + * @return int[][] Array containing an array of term ids. + * + * @phpstan-param -1|positive-int $limit + */ + public function get_term_ids_without_lang( $limit = 5 ) { + $terms = array(); + + foreach ( $this->model->get_translated_taxonomies() as $taxonomy ) { + $term_ids_with_no_language = $this->model->get_terms_with_no_lang( $taxonomy, $limit ); + + if ( ! empty( $term_ids_with_no_language ) ) { + foreach ( $term_ids_with_no_language as $id ) { + $terms[ $taxonomy ][] = $id; + } + } + } + + return $terms; + } + + /** + * Requires the simplexml PHP module when a wpml-config.xml has been found. + * + * @since 3.1 + * @since 3.2 Moved from PLL_WPML_Config + * + * @param array $modules An associative array of modules to test for. + * @return array + */ + public function site_status_test_php_modules( $modules ) { + $files = PLL_WPML_Config::instance()->get_files(); + if ( ! empty( $files ) ) { + $modules['simplexml'] = array( + 'extension' => 'simplexml', + 'required' => true, + ); + } + return $modules; + } +} diff --git a/wp-content/plugins/polylang/modules/site-health/load.php b/wp-content/plugins/polylang/modules/site-health/load.php new file mode 100644 index 0000000..09edf1f --- /dev/null +++ b/wp-content/plugins/polylang/modules/site-health/load.php @@ -0,0 +1,14 @@ +model->has_languages() ) { + $polylang->site_health = new PLL_Admin_Site_Health( $polylang ); +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php b/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php new file mode 100644 index 0000000..a74e60e --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php @@ -0,0 +1,37 @@ + 'class-wp-sitemaps-posts' ); + return $whitelist; + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/load.php b/wp-content/plugins/polylang/modules/sitemaps/load.php new file mode 100644 index 0000000..9185676 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/load.php @@ -0,0 +1,17 @@ +model->has_languages() ) { + if ( $polylang->links_model instanceof PLL_Links_Abstract_Domain ) { + $polylang->sitemaps = new PLL_Sitemaps_Domain( $polylang ); + } else { + $polylang->sitemaps = new PLL_Sitemaps( $polylang ); + } + $polylang->sitemaps->init(); +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php b/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php new file mode 100644 index 0000000..adf6e45 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php @@ -0,0 +1,218 @@ +name = $provider->name; + $this->object_type = $provider->object_type; + + $this->provider = $provider; + $this->links_model = &$links_model; + $this->model = &$links_model->model; + } + + /** + * Gets a URL list for a sitemap. + * + * @since 2.8 + * + * @param int $page_num Page of results. + * @param string $object_subtype Optional. Object subtype name. Default empty. + * @return array Array of URLs for a sitemap. + */ + public function get_url_list( $page_num, $object_subtype = '' ) { + return $this->provider->get_url_list( $page_num, $object_subtype ); + } + + /** + * Gets the max number of pages available for the object type. + * + * @since 2.8 + * + * @param string $object_subtype Optional. Object subtype. Default empty. + * @return int Total number of pages. + */ + public function get_max_num_pages( $object_subtype = '' ) { + return $this->provider->get_max_num_pages( $object_subtype ); + } + + /** + * Filters the query arguments to add the language. + * + * @since 2.8 + * + * @param array $args Sitemap provider WP_Query or WP_Term_Query arguments. + * @return array + */ + public static function query_args( $args ) { + if ( ! empty( self::$filter_lang ) ) { + $args['lang'] = self::$filter_lang; + } + return $args; + } + + /** + * Gets data for a given sitemap type. + * + * @since 2.8 + * + * @param string $object_subtype_name Object subtype name if any. + * @param string $lang Optional language name. + * @return array + */ + protected function get_sitemap_data( $object_subtype_name, $lang = '' ) { + $object_subtype_name = (string) $object_subtype_name; + + if ( ! empty( $lang ) ) { + self::$filter_lang = $lang; + } + + $return = array( + 'name' => implode( '-', array_filter( array( $object_subtype_name, $lang ) ) ), + 'pages' => $this->get_max_num_pages( $object_subtype_name ), + ); + + self::$filter_lang = ''; + return $return; + } + + /** + * Gets data about each sitemap type. + * + * @since 2.8 + * + * @return array[] Array of sitemap types including object subtype name and number of pages. + */ + public function get_sitemap_type_data() { + $sitemap_data = array(); + + add_filter( 'wp_sitemaps_posts_query_args', array( self::class, 'query_args' ) ); + add_filter( 'wp_sitemaps_taxonomies_query_args', array( self::class, 'query_args' ) ); + + $object_subtypes = $this->get_object_subtypes(); + + if ( empty( $object_subtypes ) ) { + foreach ( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) as $language ) { + $sitemap_data[] = $this->get_sitemap_data( '', $language ); + } + } + + switch ( $this->provider->name ) { + case 'posts': + $func = array( $this->model, 'is_translated_post_type' ); + break; + case 'taxonomies': + $func = array( $this->model, 'is_translated_taxonomy' ); + break; + default: + return $sitemap_data; + } + + foreach ( array_keys( $object_subtypes ) as $object_subtype_name ) { + if ( call_user_func( $func, $object_subtype_name ) ) { + foreach ( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) as $language ) { + $sitemap_data[] = $this->get_sitemap_data( $object_subtype_name, $language ); + } + } else { + $sitemap_data[] = $this->get_sitemap_data( $object_subtype_name ); + } + } + + return $sitemap_data; + } + + /** + * Gets the URL of a sitemap entry. + * + * @since 2.8 + * + * @param string $name The name of the sitemap. + * @param int $page The page of the sitemap. + * @return string The composed URL for a sitemap entry. + */ + public function get_sitemap_url( $name, $page ) { + // Check if a language was added in $name. + $pattern = '#(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')$#'; + if ( preg_match( $pattern, $name, $matches ) ) { + $lang = $this->model->get_language( $matches[1] ); + + if ( ! empty( $lang ) ) { + $name = preg_replace( '#(-?' . $lang->slug . ')$#', '', $name ); + $url = $this->provider->get_sitemap_url( $name, $page ); + return $this->links_model->add_language_to_link( $url, $lang ); + } + } + + // If no language is present in $name, we may attempt to get the current sitemap url (e.g. in redirect_canonical() ). + if ( get_query_var( 'lang' ) ) { + $lang = $this->model->get_language( get_query_var( 'lang' ) ); + $url = $this->provider->get_sitemap_url( $name, $page ); + return $this->links_model->add_language_to_link( $url, $lang ); + } + + return $this->provider->get_sitemap_url( $name, $page ); + } + + /** + * Returns the list of supported object subtypes exposed by the provider. + * + * @since 2.8 + * + * @return array List of object subtypes objects keyed by their name. + */ + public function get_object_subtypes() { + return $this->provider->get_object_subtypes(); + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php b/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php new file mode 100644 index 0000000..6294547 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php @@ -0,0 +1,71 @@ +links_model = &$polylang->links_model; + } + + /** + * Setups actions and filters. + * + * @since 3.0 + * + * @return void + */ + public function init() { + parent::init(); + + add_filter( 'wp_sitemaps_index_entry', array( $this, 'index_entry' ) ); + add_filter( 'wp_sitemaps_stylesheet_url', array( $this->links_model, 'site_url' ) ); + add_filter( 'wp_sitemaps_stylesheet_index_url', array( $this->links_model, 'site_url' ) ); + add_filter( 'home_url', array( $this, 'sitemap_url' ) ); + } + + /** + * Filters the sitemap index entries for subdomains and multiple domains. + * + * @since 2.8 + * + * @param array $sitemap_entry Sitemap entry for the post. + * @return array + */ + public function index_entry( $sitemap_entry ) { + $sitemap_entry['loc'] = $this->links_model->site_url( $sitemap_entry['loc'] ); + return $sitemap_entry; + } + + /** + * Makes sure that the sitemap urls are always evaluated on the current domain. + * + * @since 2.8.4 + * + * @param string $url A sitemap url. + * @return string + */ + public function sitemap_url( $url ) { + if ( false !== strpos( $url, '/wp-sitemap' ) ) { + $url = $this->links_model->site_url( $url ); + } + return $url; + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php b/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php new file mode 100644 index 0000000..799f614 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php @@ -0,0 +1,128 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + } + + /** + * Setups actions and filters. + * + * @since 2.8 + * + * @return void + */ + public function init() { + parent::init(); + + add_filter( 'pll_set_language_from_query', array( $this, 'set_language_from_query' ), 10, 2 ); + add_filter( 'rewrite_rules_array', array( $this, 'rewrite_rules' ) ); + add_filter( 'wp_sitemaps_add_provider', array( $this, 'replace_provider' ) ); + } + + /** + * Assigns the current language to the default language when the sitemap url + * doesn't include any language. + * + * @since 2.8 + * + * @param string|bool $lang Current language code, false if not set yet. + * @param WP_Query $query Main WP query object. + * @return string|bool + */ + public function set_language_from_query( $lang, $query ) { + if ( isset( $query->query['sitemap'] ) && empty( $query->query['lang'] ) ) { + $lang = $this->options['default_lang']; + } + return $lang; + } + + /** + * Filters the sitemaps rewrite rules to take the languages into account. + * + * @since 2.8 + * + * @param string[] $rules Rewrite rules. + * @return string[] Modified rewrite rules. + */ + public function rewrite_rules( $rules ) { + global $wp_rewrite; + + $languages = $this->model->get_languages_list( + array( + 'fields' => 'slug', + 'hide_default' => $this->options['hide_default'], + ) + ); + + if ( empty( $languages ) ) { + return $rules; + } + + $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '^' : '^language/' ) . '(' . implode( '|', $languages ) . ')/'; + + $newrules = array(); + + foreach ( $rules as $key => $rule ) { + if ( false !== strpos( $rule, 'sitemap=$matches[1]' ) ) { + $newrules[ str_replace( '^wp-sitemap', $slug . 'wp-sitemap', $key ) ] = str_replace( + array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ), + array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ), + $rule + ); // Should be enough! + } + + $newrules[ $key ] = $rule; + } + return $newrules; + } + + /** + * Replaces a sitemap provider by our decorator. + * + * @since 2.8 + * + * @param WP_Sitemaps_Provider $provider Instance of a WP_Sitemaps_Provider. + * @return WP_Sitemaps_Provider + */ + public function replace_provider( $provider ) { + if ( $provider instanceof WP_Sitemaps_Provider ) { + $provider = new PLL_Multilingual_Sitemaps_Provider( $provider, $this->links_model ); + } + return $provider; + } +} diff --git a/wp-content/plugins/polylang/modules/sync/admin-sync.php b/wp-content/plugins/polylang/modules/sync/admin-sync.php new file mode 100644 index 0000000..d071866 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/admin-sync.php @@ -0,0 +1,248 @@ +links = &$polylang->links; + + add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 3 ); + add_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ) ); + add_filter( 'use_block_editor_for_post', array( $this, 'new_post_translation' ), 5000 ); // After content duplication. + } + + /** + * Translates the post parent if it exists when using "Add new" (translation). + * + * @since 0.6 + * + * @param int $post_parent Post parent ID. + * @param int $post_id Post ID, unused. + * @param array $postarr Array of parsed post data. + * @return int + */ + public function wp_insert_post_parent( $post_parent, $post_id, $postarr ) { + $context_data = $this->links->get_data_from_new_post_translation_request( $postarr['post_type'] ?? '' ); + + if ( empty( $context_data ) ) { + return $post_parent; + } + + // Make sure not to impact media translations created at the same time. + $parent_id = wp_get_post_parent_id( $context_data['from_post'] ); + + if ( empty( $parent_id ) ) { + return $post_parent; + } + + $tr_parent = $this->model->post->get_translation( $parent_id, $context_data['new_lang'] ); + + if ( empty( $tr_parent ) ) { + return $post_parent; + } + + return $tr_parent; + } + + /** + * Copies menu order, comment, ping status and optionally the date when creating a new translation. + * + * @since 2.5 + * + * @param array $data An array of slashed post data. + * @return array + */ + public function wp_insert_post_data( $data ) { + $context_data = $this->links->get_data_from_new_post_translation_request( $data['post_type'] ?? '' ); + + if ( empty( $context_data ) ) { + return $data; + } + + foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) { + $data[ $property ] = $context_data['from_post']->$property; + } + + // Copy the date only if the synchronization is activated. + if ( in_array( 'post_date', $this->options['sync'], true ) ) { + $data['post_date'] = $context_data['from_post']->post_date; + $data['post_date_gmt'] = $context_data['from_post']->post_date_gmt; + } + + return $data; + } + + /** + * Copies post metas and taxonomies when using "Add new" (translation). + * + * @since 2.5 + * @since 3.1 Use of use_block_editor_for_post filter instead of rest_api_init which is triggered too early in WP 5.8. + * + * @param bool $is_block_editor Whether the post can be edited or not. + * @return bool + */ + public function new_post_translation( $is_block_editor ) { + global $post; + static $done = array(); + + if ( empty( $post ) ) { + return $is_block_editor; + } + + $context_data = $this->links->get_data_from_new_post_translation_request( $post->post_type ); + + if ( empty( $context_data ) || ! empty( $done[ $context_data['from_post']->ID ] ) ) { + return $is_block_editor; + } + + $lang = $this->model->get_language( $context_data['new_lang'] ); + + if ( empty( $lang ) ) { + return $is_block_editor; + } + + $done[ $context_data['from_post']->ID ] = true; // Avoid a second duplication in the block editor. Using an array only to allow multiple phpunit tests. + + $this->taxonomies->copy( $context_data['from_post']->ID, $post->ID, $lang->slug ); + $this->post_metas->copy( $context_data['from_post']->ID, $post->ID, $lang->slug ); + + if ( is_sticky( $context_data['from_post']->ID ) ) { + stick_post( $post->ID ); + } + + return $is_block_editor; + } + + /** + * Get post fields to synchronize. + * + * @since 2.4 + * + * @param WP_Post $post Post object. + * @return array Fields to synchronize. + */ + protected function get_fields_to_sync( $post ) { + global $wpdb; + + $postarr = parent::get_fields_to_sync( $post ); + $context_data = $this->links->get_data_from_new_post_translation_request( $post->post_type ); + + // For new drafts, save the date now otherwise it is overridden by WP. Thanks to JoryHogeveen. See #32. + if ( ! empty( $context_data ) && in_array( 'post_date', $this->options['sync'], true ) ) { + unset( $postarr['post_date'] ); + unset( $postarr['post_date_gmt'] ); + + $wpdb->update( + $wpdb->posts, + array( + 'post_date' => $context_data['from_post']->post_date, + 'post_date_gmt' => $context_data['from_post']->post_date_gmt, + ), + array( 'ID' => $post->ID ) + ); + } + + if ( isset( $GLOBALS['post_type'] ) ) { + $post_type = $GLOBALS['post_type']; + } elseif ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // 2nd case for quick edit. + $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + // Make sure not to impact media translations when creating them at the same time as post + if ( in_array( 'post_parent', $this->options['sync'], true ) && ( ! isset( $post_type ) || $post_type !== $post->post_type ) ) { + unset( $postarr['post_parent'] ); + } + + return $postarr; + } + + /** + * Synchronizes post fields in translations. + * + * @since 1.2 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param int[] $translations Post translations. + */ + public function pll_save_post( $post_id, $post, $translations ) { + parent::pll_save_post( $post_id, $post, $translations ); + + // Sticky posts + if ( in_array( 'sticky_posts', $this->options['sync'] ) ) { + $stickies = get_option( 'sticky_posts' ); + if ( isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ) { // phpcs:ignore WordPress.Security.NonceVerification + $stickies = array_merge( $stickies, array_values( $translations ) ); + } else { + $stickies = array_diff( $stickies, array_values( $translations ) ); + } + update_option( 'sticky_posts', array_unique( $stickies ) ); + } + } + + /** + * Some backward compatibility with Polylang < 2.3 + * allows to call PLL()->sync->copy_post_metas() and PLL()->sync->copy_taxonomies() + * used for example in Polylang for WooCommerce + * the compatibility is however only partial as the 4th argument $sync is lost + * + * @since 2.3 + * + * @param string $func Function name + * @param array $args Function arguments + * @return mixed|void + */ + public function __call( $func, $args ) { + $obj = substr( $func, 5 ); + + if ( is_object( $this->$obj ) && method_exists( $this->$obj, 'copy' ) ) { + if ( WP_DEBUG ) { + $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions + $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func + + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions + sprintf( + '%1$s was called incorrectly in %3$s on line %4$s: the call to PLL()->sync->%1$s() has been deprecated in Polylang 2.3, use PLL()->sync->%2$s->copy() instead.' . "\nError handler", + esc_html( $func ), + esc_html( $obj ), + esc_html( $debug[ $i ]['file'] ), + absint( $debug[ $i ]['line'] ) + ) + ); + } + return call_user_func_array( array( $this->$obj, 'copy' ), $args ); + } + + $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions + sprintf( + 'Call to undefined function PLL()->sync->%1$s() in %2$s on line %3$s' . "\nError handler", + esc_html( $func ), + esc_html( $debug[0]['file'] ), + absint( $debug[0]['line'] ) + ), + E_USER_ERROR + ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/load.php b/wp-content/plugins/polylang/modules/sync/load.php new file mode 100644 index 0000000..6978cd6 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/load.php @@ -0,0 +1,26 @@ +model->has_languages() ) { + if ( $polylang instanceof PLL_Admin_Base ) { + $polylang->sync = new PLL_Admin_Sync( $polylang ); + } else { + $polylang->sync = new PLL_Sync( $polylang ); + } + + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Sync'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/sync/settings-sync.php b/wp-content/plugins/polylang/modules/sync/settings-sync.php new file mode 100644 index 0000000..7c68da1 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/settings-sync.php @@ -0,0 +1,116 @@ + 'sync', + 'title' => __( 'Synchronization', 'polylang' ), + 'description' => __( 'The synchronization options allow to maintain exact same values (or translations in the case of taxonomies and page parent) of meta content between the translations of a post or page.', 'polylang' ), + ) + ); + } + + /** + * Deactivates the module + * + * @since 1.8 + */ + public function deactivate() { + $this->options['sync'] = array(); + } + + /** + * Displays the settings form + * + * @since 1.8 + */ + protected function form() { + ?> +
      + $str ) { + printf( + '
    • ', + esc_attr( $key ), + checked( in_array( $key, $this->options['sync'] ), true, false ), + esc_html( $str ) + ); + } + ?> +
    + empty( $options['sync'] ) ? array() : array_keys( $options['sync'], 1 ) ); + } + + /** + * Get the row actions. + * + * @since 1.8 + * + * @return string[] Row actions. + */ + protected function get_actions() { + return empty( $this->options['sync'] ) ? array( 'configure' ) : array( 'configure', 'deactivate' ); + } + + /** + * Get the list of synchronization settings. + * + * @since 1.0 + * + * @return string[] Array synchronization options. + * + * @phpstan-return non-empty-array + */ + public static function list_metas_to_sync() { + return array( + 'taxonomies' => __( 'Taxonomies', 'polylang' ), + 'post_meta' => __( 'Custom fields', 'polylang' ), + 'comment_status' => __( 'Comment status', 'polylang' ), + 'ping_status' => __( 'Ping status', 'polylang' ), + 'sticky_posts' => __( 'Sticky posts', 'polylang' ), + 'post_date' => __( 'Published date', 'polylang' ), + 'post_format' => __( 'Post format', 'polylang' ), + 'post_parent' => __( 'Page parent', 'polylang' ), + '_wp_page_template' => __( 'Page template', 'polylang' ), + 'menu_order' => __( 'Page order', 'polylang' ), + '_thumbnail_id' => __( 'Featured image', 'polylang' ), + ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-metas.php b/wp-content/plugins/polylang/modules/sync/sync-metas.php new file mode 100644 index 0000000..01acd11 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-metas.php @@ -0,0 +1,406 @@ +model = &$polylang->model; + + add_filter( "add_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + add_filter( "update_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + add_filter( "delete_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + + $this->add_all_meta_actions(); + + add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 ); + } + + /** + * Removes "added_{$this->meta_type}_meta" action + * + * @since 2.3 + * + * @return void + */ + protected function remove_add_meta_action() { + remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ) ); + } + + /** + * Removes all meta synchronization actions and filters + * + * @since 2.3 + * + * @return void + */ + public function remove_all_meta_actions() { + $this->remove_add_meta_action(); + + remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999 ); + remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ) ); + + remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ) ); + remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ) ); + } + + /** + * Adds "added_{$this->meta_type}_meta" action + * + * @since 2.3 + * + * @return void + */ + protected function restore_add_meta_action() { + add_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 ); + } + + /** + * Adds meta synchronization actions and filters + * + * @since 2.3 + * + * @return void + */ + public function add_all_meta_actions() { + $this->restore_add_meta_action(); + + add_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 ); // Very late in case a filter prevents the meta to be updated + add_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 ); + + add_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 ); + add_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 ); + } + + /** + * Maybe modify ("translate") a meta value when it is copied or synchronized + * + * @since 2.3 + * + * @param mixed $value Meta value + * @param string $key Meta key + * @param int $from Id of the source + * @param int $to Id of the target + * @param string $lang Language of target + * @return mixed + */ + protected function maybe_translate_value( $value, $key, $from, $to, $lang ) { + /** + * Filter a meta value before is copied or synchronized + * + * @since 2.3 + * + * @param mixed $value Meta value + * @param string $key Meta key + * @param string $lang Language of target + * @param int $from Id of the source + * @param int $to Id of the target + */ + return apply_filters( "pll_translate_{$this->meta_type}_meta", $value, $key, $lang, $from, $to ); + } + + /** + * Get the custom fields to copy or synchronize. + * + * @since 2.3 + * + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @return string[] List of meta keys. + */ + protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) { + /** + * Filters the custom fields to copy or synchronize. + * + * @since 0.6 + * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added. + * + * @param string[] $keys List of custom fields names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + */ + return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) ); + } + + /** + * Disallow modifying synchronized meta if the current user can not modify translations + * + * @since 2.6 + * + * @param null|bool $check Whether to allow adding/updating/deleting metadata. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @return null|bool + */ + public function can_synchronize_metadata( $check, $id, $meta_key ) { + if ( ! $this->model->{$this->meta_type}->current_user_can_synchronize( $id ) ) { + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + if ( in_array( $meta_key, $to_copy ) ) { + return false; + } + } + } + } + return $check; + } + + /** + * Synchronize added metas across translations + * + * @since 2.3 + * + * @param int $mid Meta id. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @return void + */ + public function add_meta( $mid, $id, $meta_key, $meta_value ) { + static $avoid_recursion = false; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + if ( in_array( $meta_key, $to_copy ) ) { + $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang ); + add_metadata( $this->meta_type, $tr_id, wp_slash( $meta_key ), is_object( $meta_value ) ? $meta_value : wp_slash( $meta_value ) ); + } + } + } + + $avoid_recursion = false; + } + } + + /** + * Stores the previous value when updating metas + * + * @since 2.3 + * + * @param null|bool $r Not used + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @param mixed $prev_value If specified, only update existing metadata entries with the specified value. + * @return null|bool Unchanged + */ + public function update_metadata( $r, $id, $meta_key, $meta_value, $prev_value ) { + if ( null === $r ) { + $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) ); + $this->prev_value[ $hash ] = $prev_value; + } + return $r; + } + + /** + * Synchronize updated metas across translations + * + * @since 2.3 + * + * @param int $mid Meta id. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @return void + */ + public function update_meta( $mid, $id, $meta_key, $meta_value ) { + static $avoid_recursion = false; + $id = (int) $id; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) ); + + $prev_meta = get_metadata_by_mid( $this->meta_type, $mid ); + + if ( $prev_meta ) { + $this->remove_add_meta_action(); // We don't want to sync back the new metas + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id && in_array( $meta_key, $this->get_metas_to_copy( $id, $tr_id, $lang, true ) ) ) { + if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) { + $prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang ); + $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang ); + update_metadata( $this->meta_type, $tr_id, wp_slash( $meta_key ), is_object( $meta_value ) ? $meta_value : wp_slash( $meta_value ), $prev_value ); + } + } + } + $this->restore_add_meta_action(); + } + + unset( $this->prev_value[ $hash ] ); + $avoid_recursion = false; + } + } + + /** + * Store metas to synchronize before deleting them. + * + * @since 2.3 + * + * @param int[] $mids Not used. + * @param int $id Object ID. + * @return void + */ + public function store_metas_to_sync( $mids, $id ) { + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + $this->to_copy[ $id ][ $tr_id ] = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + } + } + + /** + * Synchronizes deleted meta across translations. + * + * @since 2.3 + * + * @param int[] $mids Not used. + * @param int $id Object ID. + * @param string $key Meta key. + * @param mixed $value Meta value. + * @return void + */ + public function delete_meta( $mids, $id, $key, $value ) { + static $avoid_recursion = false; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + if ( in_array( $key, $this->to_copy[ $id ][ $tr_id ] ) ) { + if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP + $value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang ); + } + delete_metadata( $this->meta_type, $tr_id, wp_slash( $key ), is_object( $value ) ? $value : wp_slash( $value ) ); + } + } + } + } + + $avoid_recursion = false; + } + + /** + * Copy or synchronize metas + * + * @since 2.3 + * + * @param int $from Id of the source object + * @param int $to Id of the target object + * @param string $lang Language code of the target object + * @param bool $sync Optional, defaults to true. True if it is synchronization, false if it is a copy + * @return void + */ + public function copy( $from, $to, $lang, $sync = false ) { + $this->remove_all_meta_actions(); + + $to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync ); + $metas = get_metadata( $this->meta_type, $from ); + $metas = is_array( $metas ) ? $metas : array(); + $tr_metas = get_metadata( $this->meta_type, $to ); + $tr_metas = is_array( $tr_metas ) ? $tr_metas : array(); + + foreach ( $to_copy as $key ) { + if ( empty( $metas[ $key ] ) ) { + if ( ! empty( $tr_metas[ $key ] ) ) { + // If the meta key is not present in the source object, delete all values + delete_metadata( $this->meta_type, $to, wp_slash( $key ) ); + } + } elseif ( ! empty( $tr_metas[ $key ] ) && 1 === count( $metas[ $key ] ) && 1 === count( $tr_metas[ $key ] ) ) { + // One custom field to update + $value = reset( $metas[ $key ] ); + $value = maybe_unserialize( $value ); + $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang ); + update_metadata( $this->meta_type, $to, wp_slash( $key ), is_object( $to_value ) ? $to_value : wp_slash( $to_value ) ); + } else { + // Multiple custom fields, either in the source or the target + if ( ! empty( $tr_metas[ $key ] ) ) { + // The synchronization of multiple values custom fields is easier if we delete all metas first + delete_metadata( $this->meta_type, $to, wp_slash( $key ) ); + } + + foreach ( $metas[ $key ] as $value ) { + $value = maybe_unserialize( $value ); + $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang ); + add_metadata( $this->meta_type, $to, wp_slash( $key ), is_object( $to_value ) ? $to_value : wp_slash( $to_value ) ); + } + } + } + + $this->add_all_meta_actions(); + } + + /** + * If synchronized custom fields were previously not synchronized, it is expected + * that saving a post (or term) will synchronize them. + * + * @since 2.3 + * + * @param int $object_id Id of the object being saved. + * @param object $obj Not used. + * @param int[] $translations The list of translations object ids. + * @return void + */ + public function save_object( $object_id, $obj, $translations ) { + foreach ( $translations as $tr_lang => $tr_id ) { + if ( $tr_id != $object_id ) { + $this->copy( $object_id, $tr_id, $tr_lang, true ); + } + } + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-post-metas.php b/wp-content/plugins/polylang/modules/sync/sync-post-metas.php new file mode 100644 index 0000000..47c66cc --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-post-metas.php @@ -0,0 +1,94 @@ +meta_type = 'post'; + + parent::__construct( $polylang ); + + $this->options = &$polylang->options; + + add_filter( 'pll_translate_post_meta', array( $this, 'translate_thumbnail_id' ), 10, 3 ); + } + + /** + * Get the custom fields to copy or synchronize. + * + * @since 2.3 + * + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @return string[] List of meta keys. + */ + protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) { + $keys = array(); + + // Get public meta keys ( including from translated post in case we just deleted a custom field ). + if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) { + $from_keys = (array) get_post_custom_keys( $from ); + $to_keys = (array) get_post_custom_keys( $to ); + + $keys = array_unique( array_merge( $from_keys, $to_keys ) ); + foreach ( $keys as $k => $meta_key ) { + if ( is_protected_meta( $meta_key ) ) { + unset( $keys[ $k ] ); + } + } + } + + // Add page template and featured image. + foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) { + if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) { + $keys[] = $meta; + } + } + + if ( $this->options['media_support'] ) { + $keys[] = '_wp_attached_file'; + $keys[] = '_wp_attachment_metadata'; + $keys[] = '_wp_attachment_backup_sizes'; + $keys[] = '_wp_attachment_is_custom_header'; // Random header image. + } + + /** This filter is documented in modules/sync/sync-metas.php */ + return array_unique( apply_filters( 'pll_copy_post_metas', $keys, $sync, $from, $to, $lang ) ); + } + + /** + * Translates the thumbnail id. + * + * @since 2.3 + * + * @param int $value Thumbnail id. + * @param string $key Meta key. + * @param string $lang Language code. + * @return int + */ + public function translate_thumbnail_id( $value, $key, $lang ) { + return ( $this->options['media_support'] && '_thumbnail_id' === $key && $to_value = $this->model->post->get_translation( $value, $lang ) ) ? $to_value : $value; + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-tax.php b/wp-content/plugins/polylang/modules/sync/sync-tax.php new file mode 100644 index 0000000..4d969eb --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-tax.php @@ -0,0 +1,310 @@ +model = &$polylang->model; + $this->options = &$polylang->options; + + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + add_action( 'pll_save_term', array( $this, 'create_term' ), 10, 3 ); + add_action( 'pre_delete_term', array( $this, 'pre_delete_term' ) ); + add_action( 'delete_term', array( $this, 'delete_term' ) ); + } + + /** + * Get the list of taxonomies to copy or to synchronize. + * + * @since 1.7 + * @since 2.1 The `$from`, `$to`, `$lang` parameters were added. + * @since 3.2 Changed visibility from protected to public. + * + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information, optional, defaults to null. + * @param int $to Id of the post to which we paste information, optional, defaults to null. + * @param string $lang Language slug, optional, defaults to null. + * @return string[] List of taxonomy names. + */ + public function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) { + $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array(); + if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) { + $taxonomies[] = 'post_format'; + } + + /** + * Filters the taxonomies to copy or synchronize. + * + * @since 1.7 + * @since 2.1 The `$from`, `$to`, `$lang` parameters were added. + * + * @param string[] $taxonomies List of taxonomy names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + */ + return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) ); + } + + /** + * When copying or synchronizing terms, translate terms in translatable taxonomies + * + * @since 2.3 + * + * @param int $object_id Object ID. + * @param int[] $terms List of terms ids assigned to the source post. + * @param string $taxonomy Taxonomy name. + * @param string $lang Language slug. + * @return int[] List of terms ids to assign to the target post. + */ + protected function maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ) { + if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomy ) ) { + $newterms = array(); + + // Convert to term ids if we got tag names + $strings = array_map( 'is_string', $terms ); + if ( in_array( true, $strings, true ) ) { + $terms = get_the_terms( $object_id, $taxonomy ); + $terms = wp_list_pluck( $terms, 'term_id' ); + } + + foreach ( $terms as $term ) { + /** + * Filter the translated term when a post translation is created or synchronized + * + * @since 2.3 + * + * @param int $tr_term Translated term id + * @param int $term Source term id + * @param string $lang Language slug + */ + if ( $term_id = apply_filters( 'pll_maybe_translate_term', (int) $this->model->term->get_translation( $term, $lang ), $term, $lang ) ) { + $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags + } + } + + return $newterms; + } + + return $terms; // Empty $terms or untranslated taxonomy + } + + /** + * Maybe copy taxonomy terms from one post to the other. + * + * @since 2.6 + * + * @param int $object_id Source object ID. + * @param int $tr_id Target object ID. + * @param string $lang Target language. + * @param array $terms An array of object terms. + * @param string $taxonomy Taxonomy slug. + * @param bool $append Whether to append new terms to the old terms. + * @return void + */ + protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) { + $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang ); + + if ( in_array( $taxonomy, $to_copy ) ) { + $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ); + + // For some reasons, the user may have untranslated terms in the translation. Don't forget them. + if ( $this->model->is_translated_taxonomy( $taxonomy ) ) { + $tr_terms = get_the_terms( $tr_id, $taxonomy ); + if ( is_array( $tr_terms ) ) { + foreach ( $tr_terms as $term ) { + if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) { + $newterms[] = (int) $term->term_id; + } + } + } + } + + wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append ); + } + } + + /** + * When assigning terms to a post, assign translated terms to the translated posts (synchronisation). + * + * @since 2.3 + * + * @param int $object_id Object ID. + * @param array $terms An array of object terms. + * @param int[] $tt_ids An array of term taxonomy IDs. + * @param string $taxonomy Taxonomy slug. + * @param bool $append Whether to append new terms to the old terms. + * @return void + */ + public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) { + static $avoid_recursion = false; + $taxonomy_object = get_taxonomy( $taxonomy ); + + // Make sure that the taxonomy is registered for a post type + if ( ! $avoid_recursion && ! empty( $taxonomy_object ) && array_filter( $taxonomy_object->object_type, 'post_type_exists' ) ) { + $avoid_recursion = true; + + $tr_ids = $this->model->post->get_translations( $object_id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id !== $object_id ) { + if ( $this->model->post->current_user_can_synchronize( $object_id ) ) { + $this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ); + } else { + // No permission to synchronize, so let's synchronize in reverse order + $orig_lang = array_search( $object_id, $tr_ids ); + $tr_terms = get_the_terms( $tr_id, $taxonomy ); + + if ( false === $tr_terms ) { + $tr_terms = array(); + } + + if ( is_string( $orig_lang ) && is_array( $tr_terms ) ) { + $tr_terms = wp_list_pluck( $tr_terms, 'term_id' ); + $this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append ); + } + break; + } + } + } + + $avoid_recursion = false; + } + } + + /** + * Copy terms from one post to a translation, does not sync + * + * @since 2.3 + * + * @param int $from Id of the source post + * @param int $to Id of the target post + * @param string $lang Language slug + * @return void + */ + public function copy( $from, $to, $lang ) { + remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) ); + + // Get taxonomies to sync for this post type + $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) ); + + // Update the term cache to reduce the number of queries in the loop + update_object_term_cache( array( $from ), get_post_type( $from ) ); + + // Copy + foreach ( $taxonomies as $tax ) { + if ( $terms = get_the_terms( $from, $tax ) ) { + $terms = array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) ); + $newterms = $this->maybe_translate_terms( $from, $terms, $tax, $lang ); + + if ( ! empty( $newterms ) ) { + wp_set_object_terms( $to, $newterms, $tax ); + } + } + } + + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + } + + /** + * When creating a new term, associate it to posts having translations associated to the translated terms. + * + * @since 2.3 + * + * @param int $term_id Id of the created term. + * @param string $taxonomy Taxonomy. + * @param int[] $translations Ids of the translations of the created term. + * @return void + */ + public function create_term( $term_id, $taxonomy, $translations ) { + if ( doing_action( 'create_term' ) && in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) { + // Get all posts associated to the translated terms + $tr_posts = get_posts( + array( + 'numberposts' => -1, + 'nopaging' => true, + 'post_type' => 'any', + 'post_status' => 'any', + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => $taxonomy, + 'field' => 'id', + 'terms' => array_merge( array( $term_id ), array_values( $translations ) ), + 'include_children' => false, + ), + ), + ) + ); + + $lang = $this->model->term->get_language( $term_id ); // Language of the created term + $posts = array(); + + foreach ( $tr_posts as $post_id ) { + $post = $this->model->post->get_translation( $post_id, $lang ); + + if ( $post ) { + $posts[] = $post; + } + } + + $posts = array_unique( $posts ); + + foreach ( $posts as $post_id ) { + if ( current_user_can( 'assign_term', $term_id ) ) { + wp_set_object_terms( $post_id, $term_id, $taxonomy, true ); + } + } + } + } + + /** + * Deactivate the synchronization of terms before deleting a term + * to avoid translated terms to be removed from translated posts + * + * @since 2.3.2 + * + * @return void + */ + public function pre_delete_term() { + remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) ); + } + + /** + * Re-activate the synchronization of terms after a term is deleted + * + * @since 2.3.2 + * + * @return void + */ + public function delete_term() { + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-term-metas.php b/wp-content/plugins/polylang/modules/sync/sync-term-metas.php new file mode 100644 index 0000000..e26088f --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-term-metas.php @@ -0,0 +1,25 @@ +meta_type = 'term'; + + parent::__construct( $polylang ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync.php b/wp-content/plugins/polylang/modules/sync/sync.php new file mode 100644 index 0000000..e3f515a --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync.php @@ -0,0 +1,278 @@ +model = &$polylang->model; + $this->options = &$polylang->options; + + $this->taxonomies = new PLL_Sync_Tax( $polylang ); + $this->post_metas = new PLL_Sync_Post_Metas( $polylang ); + $this->term_metas = new PLL_Sync_Term_Metas( $polylang ); + + add_filter( 'wp_insert_post_parent', array( $this, 'can_sync_post_parent' ), 10, 3 ); + add_filter( 'wp_insert_post_data', array( $this, 'can_sync_post_data' ), 10, 2 ); + + add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 ); + add_action( 'created_term', array( $this, 'sync_term_parent' ), 10, 3 ); + add_action( 'edited_term', array( $this, 'sync_term_parent' ), 10, 3 ); + + add_action( 'pll_duplicate_term', array( $this->term_metas, 'copy' ), 10, 3 ); + + if ( $this->options['media_support'] ) { + add_action( 'pll_translate_media', array( $this->taxonomies, 'copy' ), 10, 3 ); + add_action( 'pll_translate_media', array( $this->post_metas, 'copy' ), 10, 3 ); + add_action( 'edit_attachment', array( $this, 'edit_attachment' ) ); + } + + add_filter( 'pre_update_option_sticky_posts', array( $this, 'sync_sticky_posts' ), 10, 2 ); + } + + /** + * Get post fields to synchronize. + * + * @since 2.4 + * + * @param WP_Post $post Post object. + * @return array + */ + protected function get_fields_to_sync( $post ) { + $postarr = array(); + + foreach ( array( 'comment_status', 'ping_status', 'menu_order' ) as $property ) { + if ( in_array( $property, $this->options['sync'] ) ) { + $postarr[ $property ] = $post->$property; + } + } + + if ( in_array( 'post_date', $this->options['sync'] ) ) { + $postarr['post_date'] = $post->post_date; + $postarr['post_date_gmt'] = $post->post_date_gmt; + } + + if ( in_array( 'post_parent', $this->options['sync'] ) ) { + $postarr['post_parent'] = wp_get_post_parent_id( $post->ID ); + } + + return $postarr; + } + + /** + * Prevents synchronized post parent modification if the current user hasn't enough rights + * + * @since 2.6 + * + * @param int $post_parent Post parent ID + * @param int $post_id Post ID, unused + * @param array $postarr Array of parsed post data + * @return int + */ + public function can_sync_post_parent( $post_parent, $post_id, $postarr ) { + if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) { + $tr_ids = $this->model->post->get_translations( $postarr['ID'] ); + foreach ( $tr_ids as $tr_id ) { + if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) { + $post_parent = $post->post_parent; + break; + } + } + } + return $post_parent; + } + + /** + * Prevents synchronized post data modification if the current user hasn't enough rights + * + * @since 2.6 + * + * @param array $data An array of slashed post data. + * @param array $postarr An array of sanitized, but otherwise unmodified post data. + * @return array + */ + public function can_sync_post_data( $data, $postarr ) { + if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) { + foreach ( $this->model->post->get_translations( $postarr['ID'] ) as $tr_id ) { + if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) { + $to_sync = $this->get_fields_to_sync( $post ); + $data = array_merge( $data, $to_sync ); + break; + } + } + } + return $data; + } + + /** + * Synchronizes post fields in translations. + * + * @since 2.4 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param int[] $translations Post translations. + * @return void + */ + public function pll_save_post( $post_id, $post, $translations ) { + global $wpdb; + + if ( $this->model->post->current_user_can_synchronize( $post_id ) ) { + $postarr = $this->get_fields_to_sync( $post ); + + if ( ! empty( $postarr ) ) { + foreach ( $translations as $lang => $tr_id ) { + if ( ! $tr_id || $tr_id === $post_id ) { + continue; + } + + $tr_arr = $postarr; + unset( $tr_arr['post_parent'] ); + + // Do not update the translation parent if the user set a parent with no translation. + if ( isset( $postarr['post_parent'] ) ) { + $post_parent = $postarr['post_parent'] ? $this->model->post->get_translation( $postarr['post_parent'], $lang ) : 0; + if ( ! ( $postarr['post_parent'] && ! $post_parent ) ) { + $tr_arr['post_parent'] = $post_parent; + } + } + + // Update all the rows at once. + if ( ! empty( $tr_arr ) ) { + // Don't use wp_update_post to avoid infinite loop. + $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) ); + clean_post_cache( $tr_id ); + } + } + } + } + } + + /** + * Synchronize term parent in translations. + * Calling clean_term_cache *after* this is mandatory otherwise the $taxonomy_children option is not correctly updated + * + * @since 2.3 + * + * @param int $term_id Term id. + * @param int $tt_id Term taxonomy id, not used. + * @param string $taxonomy Taxonomy name. + * @return void + */ + public function sync_term_parent( $term_id, $tt_id, $taxonomy ) { + global $wpdb; + + if ( ! is_taxonomy_hierarchical( $taxonomy ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return; + } + + $term = get_term( $term_id ); + if ( ! $term instanceof WP_Term ) { + return; + } + + $translations = $this->model->term->get_translations( $term_id ); + + foreach ( $translations as $lang => $tr_id ) { + if ( $tr_id === $term_id ) { + continue; + } + + $tr_parent = $this->model->term->get_translation( $term->parent, $lang ); + $tr_term = get_term( (int) $tr_id, $taxonomy ); + + if ( str_starts_with( current_filter(), 'created_' ) && 0 === $tr_parent ) { + // Do not remove the existing hierarchy of translations when creating new term without parent. + continue; + } + + if ( $tr_term instanceof WP_Term && ! ( $term->parent && empty( $tr_parent ) ) ) { + $wpdb->update( + $wpdb->term_taxonomy, + array( 'parent' => $tr_parent ?: 0 ), + array( 'term_taxonomy_id' => $tr_term->term_taxonomy_id ) + ); + + clean_term_cache( $tr_id, $taxonomy ); // OK since WP 3.9. + } + } + } + + /** + * Synchronizes terms and metas in translations for media + * + * @since 1.8 + * + * @param int $post_id post id + * @return void + */ + public function edit_attachment( $post_id ) { + $this->pll_save_post( $post_id, get_post( $post_id ), $this->model->post->get_translations( $post_id ) ); + } + + /** + * Synchronize sticky posts. + * + * @since 2.3 + * + * @param int[] $value New option value. + * @param int[] $old_value Old option value. + * @return int[] + */ + public function sync_sticky_posts( $value, $old_value ) { + if ( in_array( 'sticky_posts', $this->options['sync'] ) ) { + // Stick post + if ( $sticked = array_diff( $value, $old_value ) ) { + $translations = $this->model->post->get_translations( reset( $sticked ) ); + $value = array_unique( array_merge( $value, array_values( $translations ) ) ); + } + + // Unstick post + if ( $unsticked = array_diff( $old_value, $value ) ) { + $translations = $this->model->post->get_translations( reset( $unsticked ) ); + $value = array_unique( array_diff( $value, array_values( $translations ) ) ); + } + } + + return $value; + } +} diff --git a/wp-content/plugins/polylang/modules/translate-slugs/load.php b/wp-content/plugins/polylang/modules/translate-slugs/load.php new file mode 100644 index 0000000..0a14423 --- /dev/null +++ b/wp-content/plugins/polylang/modules/translate-slugs/load.php @@ -0,0 +1,20 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Translate_Slugs'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php b/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php new file mode 100644 index 0000000..a17d741 --- /dev/null +++ b/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php @@ -0,0 +1,56 @@ + 'translate-slugs', + 'title' => __( 'Translate slugs', 'polylang' ), + 'description' => $this->get_description(), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } + + /** + * Returns the module description. + * + * @since 3.1 + * + * @return string + */ + protected function get_description() { + return __( 'Allows to translate custom post types and taxonomies slugs in URLs.', 'polylang' ); + } +} diff --git a/wp-content/plugins/polylang/modules/wizard/css/wizard.css b/wp-content/plugins/polylang/modules/wizard/css/wizard.css new file mode 100644 index 0000000..b6c6a62 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/css/wizard.css @@ -0,0 +1,951 @@ +@charset "UTF-8"; +body { + margin: 65px auto 24px; + box-shadow: none; + background: #f1f1f1; + padding: 0; + border: 0; /* fix-pro #856 override WP install.css */ +} + +#pll-logo { + border: 0; + margin: 0 0 24px; + padding: 0; + text-align: center; + font-family: sans-serif; + font-size: 64px; + text-transform: uppercase; + color: #000; + line-height: normal; +} +#pll-logo a { + display: flex; + justify-content: center; + color: #000; + text-decoration: none; +} + +#pll-logo img { + max-width: 100%; + margin-right: 16px; +} +.rtl #pll-logo img { + margin-right: 0; + margin-left: 16px; +} + +.pll-wizard-footer { + text-align: center +} + +.pll-wizard .select2-container { + text-align: left; + width: auto +} + +.pll-wizard .hidden { + display: none +} + +.pll-wizard-content { + box-shadow: 0 1px 3px rgba(0, 0, 0, .13); + padding: 2em; + margin: 0 0 20px; + background: #fff; + overflow: hidden; + zoom: 1; + text-align: left; +} +.rtl .pll-wizard-content{ + text-align: right; +} + +.pll-wizard-content h1, +.pll-wizard-content h2, +.pll-wizard-content h3, +.pll-wizard-content table { + margin: 0 0 20px; + border: 0; + padding: 0; + color: #666; + clear: none; + font-weight: 500 +} + +.pll-wizard-content p { + margin: 20px 0; + font-size: 1em; + line-height: 1.75em; + color: #666 +} + +.pll-wizard-content table { + font-size: 1em; + line-height: 1.75em; + color: #666; + width: 100%; + margin-top: 20px; +} +.pll-wizard-content table td span{ + display: inline-block; +} + +.pll-wizard-content table caption { + caption-side: bottom; + font-style: italic; + text-align: right; +} +.rtl .pll-wizard-content table caption { + text-align: left; +} + +.pll-wizard-content table caption .icon-default-lang{ + font-style: normal; +} + +.pll-wizard-content a { + color: #a03f3f; +} + +.pll-wizard-content a:focus, +.pll-wizard-content a:hover, +.pll-wizard-footer-links:hover { + color: #dd5454 +} + +.pll-wizard-content .pll-wizard-next-steps { + overflow: hidden; + margin: 0 0 24px; + padding-bottom: 2px +} + +.pll-wizard-content .pll-wizard-next-steps h2 { + margin-bottom: 12px +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-first { + float: left; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-last { + float: right; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps ul { + padding: 0 2em 0 0; + list-style: none outside; + margin: 0 +} + +.pll-wizard-content .pll-wizard-next-steps ul li a { + display: block; + padding: 0 0 .75em +} + +.pll-wizard-content .pll-wizard-next-steps ul li a::before { + color: #82878c; + font: normal 20px/1 dashicons; + speak: none; + display: inline-block; + padding: 0 10px 0 0; + top: 1px; + position: relative; + text-decoration: none!important; + vertical-align: top +} + +.pll-wizard-steps { + padding: 0 0 24px; + margin: 0; + list-style: none outside; + overflow: hidden; + color: #ccc; + width: 100%; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: inline-flex +} + +.pll-wizard-steps li { + width: 100%; + float: left; + padding: 0 0 .8em; + margin: 0; + text-align: center; + position: relative; + border-bottom: 4px solid #ccc; + line-height: 1.4em +} + +.pll-wizard-steps li a { + color: #a03f3f; + text-decoration: none; + padding: 1.5em; + margin: -1.5em; + position: relative; + z-index: 1 +} + +.pll-wizard-steps li a:focus, +.pll-wizard-steps li a:hover { + color: #dd5454; + text-decoration: underline +} + +.pll-wizard-steps li::before { + content: ""; + border: 4px solid #ccc; + border-radius: 100%; + width: 4px; + height: 4px; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -6px; + margin-bottom: -8px; + background: #fff +} + +.pll-wizard-steps li.active { + border-color: #a03f3f; + color: #a03f3f; + font-weight: 700 +} + +.pll-wizard-steps li.active::before { + border-color: #a03f3f +} + +.pll-wizard-steps li.done { + border-color: #a03f3f; + color: #a03f3f +} + +.pll-wizard-steps li.done::before { + border-color: #a03f3f; + background: #a03f3f +} + +.pll-wizard .pll-wizard-actions { + overflow: hidden; + margin: 20px 0 0; + position: relative +} + +.pll-wizard .pll-wizard-actions .button { + font-size: 16px; + font-weight: 300; + padding: 1em 2em; + line-height: 1em; + margin-right: .5em; + margin-bottom: 2px; + margin-top: 10px; + height: auto; + border-radius: 4px; + box-shadow: none; + min-width: auto; + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button { + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button-primary, +.pll-wizard .pll-wizard-actions .button-primary { + background-color: #a03f3f; + border-color: #a03f3f; + color: #fff; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #a03f3f; + text-shadow: 0 -1px 1px #a03f3f, 1px 0 1px #a03f3f, 0 1px 1px #a03f3f, -1px 0 1px #a03f3f; + margin: 0; + opacity: 1 +} + +.pll-wizard .pll-wizard-content .button-small .dashicons { + font-size: 15px; + height: auto; + vertical-align: middle; +} + +.pll-wizard .button-primary:active, +.pll-wizard .button-primary:focus, +.pll-wizard input[type="checkbox"]:focus + label.button-primary, +.pll-wizard .button-primary:hover { + background: #dd5454; + border-color: #dd5454; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #dd5454 +} +.pll-wizard .pll-wizard-actions .button-primary[disabled], +.pll-wizard .pll-wizard-actions .button-primary:disabled, +.pll-wizard .pll-wizard-actions .button-primary.disabled { + cursor: wait; + background-color: #bb5454 !important; + border-color: #bb5454 !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #bb5454 !important; + text-shadow: 0 -1px 1px #bb5454, 1px 0 1px #bb5454, 0 1px 1px #bb5454, -1px 0 1px #bb5454 !important; + color: #ffa3a3 !important; +} +.pll-wizard-content p:last-child { + margin-bottom: 0 +} + +.pll-wizard-footer-links { + font-size: .85em; + color: #7b7b7b; + margin: 1.18em auto; + display: inline-block; + text-align: center +} + +.pll-wizard-services { + border: 1px solid #eee; + padding: 0; + margin: 0 0 1em; + list-style: none outside; + border-radius: 4px; + overflow: hidden +} + +.pll-wizard-services p { + margin: 0 0 1em 0; + padding: 0; + font-size: 1em; + line-height: 1.5em +} + +.pll-wizard-service-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + padding: 0; + border-bottom: 1px solid #eee; + color: #666; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.media-step .pll-wizard-service-item{ + border: 0; +} + +.media-step .pll-wizard-service-item:last-child{ + display: block; +} + +.media-step .pll-wizard-service-item .pll-wizard-service-enable{ + padding-bottom: 0; +} + +.pll-wizard-service-item:last-child { + border-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-name { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 160px; + text-align: center; + font-weight: 700; + padding: 2em 0; + -webkit-align-self: stretch; + align-self: stretch; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: baseline; + -webkit-align-items: baseline; + align-items: baseline +} + +.pll-wizard-service-item .pll-wizard-service-name img { + max-width: 75px +} + +.pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + padding: 20px +} + +.pll-wizard-service-item .pll-wizard-service-example { + padding: 0 20px 20px +} + +.pll-wizard-service-item .pll-wizard-service-example p{ + text-align: right; +} +.rtl .pll-wizard-service-item .pll-wizard-service-example p{ + text-align: left; +} + +.pll-wizard-service-item .pll-wizard-service-description p { + margin-bottom: 1em +} + +.pll-wizard-service-item .pll-wizard-service-description p:last-child { + margin-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-description .pll-wizard-service-settings-description { + display: block; + font-style: italic; + color: #999 +} + +.pll-wizard-service-item .pll-wizard-service-enable { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 75px; + text-align: center; + cursor: pointer; + padding: 2em 0; + position: relative; + max-height: 1.5em; + -webkit-align-self: flex-start; + align-self: flex-start; + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 +} + +.pll-wizard-service-item .pll-wizard-service-toggle { + position: relative +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] { + position:absolute; + opacity: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label { + position: relative; + display: inline-block; + width: 44px; + height: 20px; + border-radius: 10em; + cursor: pointer; + text-indent: -9999px; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:focus + label { + border:1px dashed #777; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before, +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + content: ''; + position: absolute; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before { + left: 0; + top: 0; + width: 44px; + height: 20px; + background: #ddd; + border-radius: 10em; + transition: background-color .2s; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + width: 16px; + height: 16px; + transition: all .2s; + border-radius: 50%; + background: #fff; + margin: 2px; + top: 0; + left: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::before { + background:#a03f3f; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::after { + right: 0; + left:auto; +} + +.pll-wizard-service-item .pll-wizard-service-settings { + display: none; + margin-top: .75em; + margin-bottom: 0; + cursor: default +} + +.pll-wizard-service-item .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings { + display: inline-block +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.closed { + border-bottom: 0 +} + +.step { + text-align: center +} + +.pll-wizard .button .dashicons{ + vertical-align: middle; +} +.rtl .dashicons-arrow-right-alt2:before { + content: "\f341"; +} +.pll-wizard .pll-wizard-actions .button:active, +.pll-wizard .pll-wizard-actions .button:focus, +.pll-wizard .pll-wizard-actions .button:hover { + box-shadow: none +} + +.pll-wizard-next-steps { + border: 1px solid #eee; + border-radius: 4px; + list-style: none; + padding: 0 +} + +.pll-wizard-next-steps li { + padding: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + border-top: 1px solid #eee +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border, +.pll-wizard-next-steps .pll-wizard-next-step-item:first-child { + border-top: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + margin: 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-action { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.pll-wizard-next-steps .pll-wizard-next-step-action .button { + margin: 1em 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-description, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-actions, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-action .button{ + margin-top: 0; +} + + +.pll-wizard-next-steps p.next-step-heading { + margin: 0; + font-size: .95em; + font-weight: 400; + font-variant: all-petite-caps +} + +.pll-wizard-next-steps p.next-step-extra-info { + margin: 0 +} + +.pll-wizard-next-steps h3.next-step-description { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps { + border-top: 1px solid #eee; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-next-step-description { + margin-bottom: 0 +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions { + margin: 0 0 1.5em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + font-size: 15px; + margin: 1em 0 1em 1.5em; +} +.rtl .pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + margin: 1em 1.5em 1em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button::last-child { + margin-right: 1.5em; +} + +.pll-wizard-content img{ + max-width: 100%; + margin-right: 0.5em; +} +.rtl .pll-wizard-content img{ + margin-left: 0.5em; +} + +.pll-wizard-content .form-field label{ + margin-bottom: 5px; + display: block; +} + +.pll-wizard-content .form-field select{ + padding: 3px; +} + +.pll-wizard-content .languages-step select, +.pll-wizard-content .untranslated-contents-step select{ + width: 100%; +} + +.languages-step .form-field .button{ + margin-left: 15px; +} +.languages-step .form-field .button > span{ + margin-right: 0.3em; +} +.rtl .languages-step .form-field .button{ + margin-left: 0; + margin-right: 15px; +} +.rtl .languages-step .form-field .button > span{ + margin-left: 0.3em; + margin-right: 0; +} + +.pll-wizard-content .languages-step .select-language-field{ + display: flex; +} + +.pll-wizard-content #languages{ + display: none; +} +.pll-wizard-content #languages tr th:first-child{ + width: 80%; +} +.pll-wizard-content #languages .dashicons{ + color: #a03f3f; +} +.pll-wizard-content #languages img{ + margin-right: 5px; +} +.pll-wizard-content .error{ + color: #a03f3f; + font-weight: bold; +} +.pll-wizard-content #messages .error{ + background: #fccfcf; + padding: 0.5rem; + border: 1px solid #a03f3f; + margin-bottom: 0.5rem; +} + +.pll-wizard-content #slide-toggle{ + position:absolute; + opacity: 0; +} + +.pll-wizard-content #slide-toggle + label{ + position:relative; +} +.pll-wizard-content #slide-toggle + label + span{ + display: block; +} + +.pll-wizard-content #slide-toggle + label .dashicons{ + margin-right: 0.3em; +} +.rtl .pll-wizard-content #slide-toggle + label .dashicons{ + margin-left: 0.3em; + margin-right: 0; +} +.pll-wizard-content #slide-toggle ~ #screenshot > img { + max-height: 500px; + margin-top: 10px; + -webkit-transition: all .5s cubic-bezier(0, 1, 0.5, 1); + transition: all .5s cubic-bezier(0, 1, 0.5, 1); +} +.pll-wizard-content #slide-toggle:checked ~ #screenshot > img { + max-height: 0; +} +.hide { + display: none; +} + +input[type="text"].field-in-error, +input[type="password"].field-in-error, +input[type="checkbox"].field-in-error, +input[type="color"].field-in-error, +input[type="date"].field-in-error, +input[type="datetime"].field-in-error, +input[type="datetime-local"].field-in-error, +input[type="email"].field-in-error, +input[type="month"].field-in-error, +input[type="number"].field-in-error, +input[type="search"].field-in-error, +input[type="radio"].field-in-error, +input[type="tel"].field-in-error, +input[type="text"].field-in-error, +input[type="time"].field-in-error, +input[type="url"].field-in-error, +input[type="week"].field-in-error, +select.field-in-error, +textarea.field-in-error, +span.field-in-error, +.field-in-error{ + border-color: #a03f3f; +} + +input[type="text"].field-in-error:focus, +input[type="password"].field-in-error:focus, +input[type="checkbox"].field-in-error:focus, +input[type="color"].field-in-error:focus, +input[type="date"].field-in-error:focus, +input[type="datetime"].field-in-error:focus, +input[type="datetime-local"].field-in-error:focus, +input[type="email"].field-in-error:focus, +input[type="month"].field-in-error:focus, +input[type="number"].field-in-error:focus, +input[type="search"].field-in-error:focus, +input[type="radio"].field-in-error:focus, +input[type="tel"].field-in-error:focus, +input[type="text"].field-in-error:focus, +input[type="time"].field-in-error:focus, +input[type="url"].field-in-error:focus, +input[type="week"].field-in-error:focus, +select.field-in-error:focus, +textarea.field-in-error:focus, +span.field-in-error:focus, +.field-in-error:focus{ + border: 1px solid #a03f3f; + box-shadow: 0 0 2px rgba(160, 63, 63, 0.8); + outline-color: #a03f3f; + outline-style: auto; + outline-width: thin; +} + +/* override install styles by returning back to forms styles */ +.form-table input.regular-text{ + width: 25em; +} +.form-table input.field-in-error{ + border-color: #a03f3f; +} +#pll-licenses-table td{ + padding: 10px 9px; +} +#pll-licenses-table .license-valid td p{ + min-width: 35em; +} +#pll-licenses-table .pll-deactivate-license{ + margin: 0 0 0 20px; +} +.rtl #pll-licenses-table .pll-deactivate-license{ + margin: 0 10px 0 0; +} +.pll-wizard-content .documentation { + padding: 24px 24px 0; + margin: 0 0 24px; + overflow: hidden; + background: #f5f5f5 +} + +.pll-wizard-content .documentation p { + padding: 0; + margin: 0 0 12px; +} +.documentation-container { + display: -webkit-box; + display: -webkit-flex; + display: flex; + justify-content: flex-end; +} + +.documentation-container .documentation-button-container { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; +} + +.wc-setup .wc-setup-actions .button.documentation-button { + height: 42px; + padding: 0 1em; + margin: 0; +} +#dialog{ + display: none; +} +.pll-wizard .ui-dialog.ui-widget-content{ + max-height: none; +} +.pll-wizard .ui-dialog-title::before{ + content: "\f534"; + font-family: dashicons; + display: inline-block; + line-height: 1; + font-weight: 400; + font-style: normal; + speak: none; + text-decoration: inherit; + text-transform: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 20px; + height: 20px; + font-size: 20px; + vertical-align: middle; + text-align: center; + margin: 0 5px 5px 0; + transition: color 0.1s ease-in; +} +.rtl.pll-wizard .ui-dialog-title::before{ + margin-right: 0; + margin-left: 5px; +} +.pll-wizard .ui-dialog ul{ + list-style: disc; + padding-left: 20px; +} +.rtl.pll-wizard .ui-dialog ul{ + padding-left: 0; + padding-right: 20px; +} +.pll-wizard li{ + margin-bottom: 0; +} +#translations{ + border-collapse: collapse; +} +#translations tbody:nth-child(odd){ + background-color: #f9f9f9; +} +#translations.striped > tbody > :nth-child(odd) { + background-color: transparent; /* Override common WordPress style */ +} +.pll-wizard-content mark{ + background: transparent none; +} +.pll-wizard-content mark{ + color: #7ad03a; +} +@media screen and (max-width: 782px) { + /* Override WordPress button css rules */ + .languages-step .form-field .button{ + font-size: 13px; + line-height: 26px; + height: 28px; + padding: 0 10px 1px; + vertical-align: top; + } + + #pll-licenses-table .pll-deactivate-license{ + margin: 10px 0 5px; + } +} +@media only screen and (max-width:620px) { + /* Override dialog width rule */ + .ui-dialog{ + width: 100% !important; + } + +} +@media only screen and (max-width:500px) { + #pll-logo a, + .select-language-field{ + flex-direction: column; + } + .select-language-field .action-buttons{ + display: flex; + justify-content: flex-end; + } + .languages-step .form-field .button{ + margin: 5px 0 0; + } +} +@media only screen and (max-width:400px) { + #pll-logo { + font-size: 56px; + } + .pll-wizard-steps { + display: none + } + .pll-wizard-service-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-service-item .pll-wizard-service-enable { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; + padding: 20px 0 0 + } + .pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 + } + .pll-wizard-service-item .pll-wizard-service-name { + padding: 20px 20px 0; + text-align: left; + -webkit-box-pack: justify!important; + -webkit-justify-content: space-between!important; + justify-content: space-between!important + } + .pll-wizard-service-item .pll-wizard-service-name img { + margin: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-description { + margin-bottom: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-action p { + margin: 0 + } +} +@media only screen and (max-width:360px) { + #pll-logo { + font-size: 48px; + } +} diff --git a/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php b/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php new file mode 100644 index 0000000..d2ef5a9 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php @@ -0,0 +1,49 @@ + 'mlang_wizard', + ), + admin_url( 'admin.php' ) +); +?> +

    + + + + +

    +

    + + + + +

    diff --git a/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png b/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png new file mode 100644 index 0000000..293e18f Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/images/media-screen.png b/wp-content/plugins/polylang/modules/wizard/images/media-screen.png new file mode 100644 index 0000000..a90bf5c Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/media-screen.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png b/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png new file mode 100644 index 0000000..55f67c2 Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/js/languages-step.js b/wp-content/plugins/polylang/modules/wizard/js/languages-step.js new file mode 100644 index 0000000..0ad845e --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/js/languages-step.js @@ -0,0 +1,302 @@ +/** + * @package Polylang + */ + +jQuery( + function ( $ ) { + var addLanguageForm = $( '.languages-step' ); // Form element. + var languageFields = $( '#language-fields' ); // Element where to append hidden fields for creating language. + var languagesTable = $( '#languages' ); // Table element contains languages list to create. + var languagesListTable = $( '#languages tbody' ); // Table rows with languages list to create. + var definedLanguagesListTable = $( '#defined-languages tbody' ); // Table rows with already defined languages list. + var languagesList = $( '#lang_list' ); // Select form element with predefined languages without already created languages. + var nextStepButton = $( '[name="save_step"]' ); // The button for continuing to the next step. + var messagesContainer = $( '#messages' ); // Element where to display error messages. + var languagesMap = new Map(); // Languages map object for managing the languages to create. + var dialog = $( '#dialog' ); // Dialog box for alerting the language selected has not been added to the list. + + /** + * Add a language in the list to create it in Polylang settings + * + * @param {object} language The language object + */ + function addLanguage( language ) { + // language properties come from the select dropdown which is built server side and well escaped. + // see template view-wizard-step-languages.php. + var languageValueHtml = $( '' ).text( language.text ).prepend( language.flagUrl ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageTrashIconHtml = $( '' ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'dashicons dashicons-trash' ) + .attr( 'data-language', language.locale ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'screen-reader-text' ) + .text( pll_wizard_params.i18n_remove_language_icon ) + ) + ); + // see the comment and the hardcoded code above. languageTrashIconHtml and languageValueHtml are safe. + var languageLineHtml = $( '' ).prepend( languageTrashIconHtml ).prepend( languageValueHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageFieldHtml = $( '' ).attr( + { + type: 'hidden', + name: 'languages[]' + } + ).val( language.locale ); + + languagesList.val( '' ); + languagesList.selectmenu( 'refresh' ); // Refresh jQuery selectmenu widget after changing the value. + + languagesMap.set( language.locale, language ); + + // see above how languageLineHtml is built. + languagesListTable.append( languageLineHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + // Bind click event on trash icon. + languagesListTable.on( + 'click', + 'span[data-language=' + language.locale + ']', + function ( event ) { + event.preventDefault(); + // Remove line in languages table. + $( this ).parents( 'tr' ).remove(); + // Remove input field. + var languageField = languageFields.children( 'input[value=' + $( this ).data( 'language' ) + ']' ).remove(); + // If there is no more languages hide languages table. + if ( languagesListTable.children().length <= 0 ) { + languagesTable.hide(); + } + // Remove language from the Map. + languagesMap.delete( $( this ).data( 'language' ) ); + // Hide error message. + hideError(); + } + ); + // see above how languageFieldHtml is built. + // Add hidden input field for posting the form. + languageFields.append( languageFieldHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + } + + /** + * Display an error message + * + * @param {string} message The message to display + */ + function showError( message ) { + messagesContainer.empty(); + // html is hardcoded and use of jQuery text method which is safe to add message value. + // In addition message is i18n value which is initialized server side in PLL_Wizard::add_step_languages and correctly escaped. + messagesContainer.prepend( $( '

    ' ).addClass( 'error' ).text( message ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + + /** + * Hide all error messages and fields in error + */ + function hideError() { + messagesContainer.empty(); + addLanguageForm.find( '.error' ).removeClass( 'error field-in-error' ); + } + + /** + * Style the field to indicate where the error is + * + * @param {object} field The jQuery element which is in error + */ + function showFieldInError( field ) { + field.addClass( 'error field-in-error' ); + } + + /** + * Focus on a specific element + * + * @param {object} field The jQuery element which will be focused + */ + function focusOnField( field ) { + field.trigger( 'focus' ); + } + + /** + * Disable a specific button + * + * @param {object} button + */ + function disableButton( button ){ + button.prop( 'disabled', true ); + // Because the button is disabled we need to add the value of the button to ensure it will pass in the request. + addLanguageForm.append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ).prop( + { + type: 'hidden', + name: button.prop( 'name' ), + value: button.prop( 'value' ) + } + ) + ); + } + + /** + * Remove error when a new selection is done in languages list. + */ + languagesList.on( + 'selectmenuchange', + function () { + hideError();; + } + ); + /** + * Bind click event on "Add language" button + */ + $( '#add-language' ).on( + 'click', + function ( event ) { + hideError(); + var selectedOption = event.currentTarget.form.lang_list.options[event.currentTarget.form.lang_list.selectedIndex]; + if ( '' !== selectedOption.value && ! languagesMap.has( selectedOption.value ) ) { + addLanguage( + { + locale: selectedOption.value, + text: selectedOption.innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + // Show table of languages. + languagesTable.show(); + // Put back the focus on the select language field after clicking on "Add language button". + focusOnField( $( '#lang_list-button' ) ); + } else { + var message = pll_wizard_params.i18n_no_language_selected; + if ( languagesMap.has( selectedOption.value ) ) { + message = pll_wizard_params.i18n_language_already_added; + } + showError( message ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + + } + } + ); + + /** + * Bind submit event on "add_lang" form + */ + addLanguageForm.on( + 'submit', + function ( event ) { + // Verify if there is at least one language. + var isLanguagesAlreadyDefined = definedLanguagesListTable.children().length > 0; + var selectedLanguage = $( '#lang_list' ).val(); + if ( languagesMap.size <= 0 && ! isLanguagesAlreadyDefined ) { + if ( '' === selectedLanguage ) { + showError( pll_wizard_params.i18n_no_language_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } else { + showError( pll_wizard_params.i18n_add_language_needed ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#add-language' ) ); // Put the focus on the "Add language" button. + } + return false; + } + // Verify if the language has been added in the list otherwise display a dialog box to confirm what to do. + if ( '' !== selectedLanguage ) { + // Verify we don't add a duplicate language before opening the dialog box otherwise display an error message. + if ( ! languagesMap.has( selectedLanguage ) ) { + dialog.dialog( 'open' ); + } else { + showError( pll_wizard_params.i18n_language_already_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } + return false; + } + disableButton( nextStepButton ); + } + ); + + // Is there an error return by PHP ? + var searchParams = new URLSearchParams( document.location.search ); + if ( searchParams.has( 'activate_error' ) ) { + // If the error code exists, display it. + if ( undefined !== pll_wizard_params[ searchParams.get( 'activate_error' ) ] ) { + showError( pll_wizard_params[ searchParams.get( 'activate_error' ) ] ); + } + } + + function confirmDialog( what ) { + switch ( what ) { + case 'yes': + var selectedOption = $( '#lang_list' ).children( ':selected' ); + addLanguage( + { + locale: selectedOption[0].value, + text: selectedOption[0].innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + break; + case 'no': + // Empty select form field and submit again the form. + languagesList.val( '' ); + break; + case 'ignore': + } + dialog.dialog( 'close' ); + if ( 'ignore' === what ) { + focusOnField( $( '#lang_list-button' ) ); + } else { + addLanguageForm.submit(); + } + } + + // Initialize dialog box in the case a language is selected but not added in the list. + dialog.dialog( + { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: pll_wizard_params.i18n_dialog_title, + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( $( 'body' ).hasClass( 'rtl' ) ) { + $( this ).parent().css( + { + right: $( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + // Display language name and flag information in dialog box. + $( this ).find( '#dialog-language' ).text( $( '#lang_list' ).children( ':selected' ).first().text() ); + // language properties come from the select dropdown #lang_list which is built server side and well escaped. + // see template view-wizard-step-languages.php. + $( this ).find( '#dialog-language-flag' ).empty().prepend( $( '#lang_list' ).children( ':selected' ).data( 'flag-html' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + }, + buttons: [ + { + text: pll_wizard_params.i18n_dialog_yes_button, + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_no_button, + click: function ( event ) { + confirmDialog( 'no' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_ignore_button, + click: function ( event ) { + confirmDialog( 'ignore' ); + } + } + ] + } + ) + } +); diff --git a/wp-content/plugins/polylang/modules/wizard/load.php b/wp-content/plugins/polylang/modules/wizard/load.php new file mode 100644 index 0000000..0febfd3 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/load.php @@ -0,0 +1,14 @@ +wizard = new PLL_Wizard( $polylang ); +} diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php new file mode 100644 index 0000000..9117a6e --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php @@ -0,0 +1,122 @@ + + +> + + + + + <?php + printf( + /* translators: %s is the plugin name */ + esc_html__( '%s › Setup', 'polylang' ), + esc_html( POLYLANG ) + ); + ?> + + + + + + + +

    + + + + +

    +
      + $step ) { + $is_completed = array_search( $current_step, array_keys( $steps ), true ) > array_search( $step_key, array_keys( $steps ), true ); + + if ( $step_key === $current_step ) { + ?> +
    1. + +
    2. + + + +
    3. + +
    4. + +
    +
    +
    "> + + +

    + +

    + +
    +
    + + + diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php new file mode 100644 index 0000000..8d689ba --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php @@ -0,0 +1,125 @@ + + + + +

    +

    + ' . esc_html( $home_page->post_title ) . '' + ); + ?> +
    + flag . ' ' . esc_html( $home_page_language->name ) . ' ' . esc_html( $home_page_language->locale ) . '' //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +
    + +

    +

    + +

    + + + + + + + + + languages->get( $lang ); + if ( empty( $language ) ) { + continue; + } + ?> + + + + + +
    + flag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ' ' . esc_html( $language->name ) . ' ' . esc_html( $language->locale ) . ' '; + ?> + is_default ) : ?> + + + + + + + +
    + + + + + = 1 ) : ?> + + + + + + + + + + + + + + + + + + + +
    + + +
    + flag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ' ' . esc_html( $lg->name ) . ' ' . esc_html( $lg->locale ) . ' '; + ?> + is_default ) : ?> + + + + + + + +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php new file mode 100644 index 0000000..6877f1e --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php @@ -0,0 +1,136 @@ +languages->get_list(); +$default_language = $model->languages->get_default(); +$languages_list = array_diff_key( + PLL_Settings::get_predefined_languages(), + wp_list_pluck( $existing_languages, 'locale', 'locale' ) +); +?> +
    +

    + +

    +

    + +

    +

    +
    +
    +
    + +
    + +
    + +
    +
    +
    + + + + + + + + + +
    + + + + + + + + + + + ' . "\n", + esc_attr( $lg->locale ), + esc_html( $lg->name ), + $lg->flag, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $lg->is_default ? ' ' . esc_html__( 'Default language', 'polylang' ) . '' : '' + ); + } + ?> + +
    %3$s%2$s - %1$s %4$s
    + +
    +

    + ', + '' + ); + ?> +

    +

    + +

    +
      +
    • + ' . esc_html__( 'Yes', 'polylang' ) . '' + ); + ?> +
    • +
    • + ' . esc_html__( 'No', 'polylang' ) . '' + ); + ?> +
    • +
    • + ' . esc_html__( 'Ignore', 'polylang' ) . '' + ); + ?> +
    • +
    +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php new file mode 100644 index 0000000..503889d --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php @@ -0,0 +1,112 @@ + +

    + +
    +

    +
    +

    + + + +

    +
    +
    + +
      +
    • +
      +

      +

      +

      + +

      +
      +
      +

      + + + +

      +
      +
    • +
    • +
      +

      +

      +

      +
      +
      +

      + + + +

      +
      +
    • + +
    • +
      +

      +

      +

      + +

      +
      +
      +

      + + + +

      +
      +
    • + + +
    • +
      +

      +

      +

      + ' . esc_html__( 'Polylang Business Pack', 'polylang' ) . '' + ); + ?> +

      +
      +
      +

      + + + +

      +
      +
    • + +
    • +
      +

      + + + +

      +
      +
    • +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php new file mode 100644 index 0000000..ec4feb6 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php @@ -0,0 +1,42 @@ + +

    + + +

    +

    +
    + +

    + +
    +
    + + + get_form_field(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> + +
    +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php new file mode 100644 index 0000000..a71924d --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php @@ -0,0 +1,60 @@ + +

    +

    + + + +

    +

    + +

    +
      +
    • +
      + + + /> + +
      +
      +

      + +

      +
      +
    • +
    • +
      +

      + + + + + +

      +
      +
    • +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php new file mode 100644 index 0000000..2c6dc79 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php @@ -0,0 +1,37 @@ +languages->get_list(); +?> +

    +

    +
    +
    + +

    +
    + + +
    diff --git a/wp-content/plugins/polylang/modules/wizard/wizard.php b/wp-content/plugins/polylang/modules/wizard/wizard.php new file mode 100644 index 0000000..e5e20a9 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/wizard.php @@ -0,0 +1,888 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + + // Display Wizard page before any other action to ensure displaying it outside the WordPress admin context. + // Hooked on admin_init with priority 40 to ensure PLL_Wizard_Pro is correctly initialized. + add_action( 'admin_init', array( $this, 'setup_wizard_page' ), 40 ); + // Add Wizard submenu. + add_filter( 'pll_settings_tabs', array( $this, 'settings_tabs' ), 10, 1 ); + // Add filter to select screens where to display the notice. + add_filter( 'pll_can_display_notice', array( $this, 'can_display_notice' ), 10, 2 ); + + // Default steps. + add_filter( 'pll_wizard_steps', array( $this, 'add_step_licenses' ), 100 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_languages' ), 200 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_media' ), 300 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_untranslated_contents' ), 400 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_home_page' ), 500 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_last' ), 999 ); + } + + /** + * Save an activation transient when Polylang is activating to redirect to the wizard + * + * @since 2.7 + * + * @param bool $network_wide if activated for all sites in the network. + * @return void + */ + public static function start_wizard( $network_wide ) { + $options = (array) get_option( Options::OPTION_NAME, array() ); + + if ( wp_doing_ajax() || $network_wide || ! empty( $options['version'] ) ) { + return; + } + set_transient( 'pll_activation_redirect', 1, 30 ); + } + + /** + * Redirect to the wizard depending on the context + * + * @since 2.7 + * + * @return void + */ + public function redirect_to_wizard() { + if ( get_transient( 'pll_activation_redirect' ) ) { + $do_redirect = true; + if ( ( isset( $_GET['page'] ) && 'mlang_wizard' === sanitize_key( $_GET['page'] ) ) || isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + delete_transient( 'pll_activation_redirect' ); + $do_redirect = false; + } + + if ( $do_redirect ) { + wp_safe_redirect( + sanitize_url( + add_query_arg( + array( + 'page' => 'mlang_wizard', + ), + admin_url( 'admin.php' ) + ) + ) + ); + exit; + } + } + } + + /** + * Add an admin Polylang submenu to access the wizard + * + * @since 2.7 + * + * @param string[] $tabs Submenus list. + * @return string[] Submenus list updated. + */ + public function settings_tabs( $tabs ) { + $tabs['wizard'] = esc_html__( 'Setup', 'polylang' ); + return $tabs; + } + + /** + * Returns true if the media step is displayable, false otherwise. + * + * @since 2.7 + * + * @param PLL_Language[] $languages List of language objects. + * @return bool + */ + public function is_media_step_displayable( $languages ) { + $media = array(); + // If there is no language or only one the media step is displayable. + if ( ! $languages || count( $languages ) < 2 ) { + return true; + } + foreach ( $languages as $language ) { + $media[ $language->slug ] = $this->model->count_posts( + $language, + array( + 'post_type' => array( 'attachment' ), + 'post_status' => 'inherit', + ) + ); + } + return count( array_filter( $media ) ) === 0; + } + + /** + * Check if the licenses step is displayable + * + * @since 2.7 + * + * @return bool + */ + public function is_licenses_step_displayable() { + $licenses = apply_filters( 'pll_settings_licenses', array() ); + return count( $licenses ) > 0; + } + + /** + * Setup the wizard page + * + * @since 2.7 + * + * @return void + */ + public function setup_wizard_page() { + + PLL_Admin_Notices::add_notice( 'wizard', $this->wizard_notice() ); + + $this->redirect_to_wizard(); + if ( ! Polylang::is_wizard() ) { + return; + } + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Sorry, you are not allowed to manage options for this site.', 'polylang' ) ); + } + + // Enqueue scripts and styles especially for the wizard. + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + $this->steps = apply_filters( 'pll_wizard_steps', $this->steps ); + $step = isset( $_GET['step'] ) ? sanitize_key( $_GET['step'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification + + $this->current_step = $step && array_key_exists( $step, $this->steps ) ? $step : current( array_keys( $this->steps ) ); + + $has_languages = $this->model->has_languages(); + + if ( ! $has_languages && ! in_array( $this->current_step, array( 'licenses', 'languages' ) ) ) { + wp_safe_redirect( sanitize_url( $this->get_step_link( 'languages' ) ) ); + exit; + } + + if ( $has_languages && $this->model->get_objects_with_no_lang( 1 ) && ! in_array( $this->current_step, array( 'licenses', 'languages', 'media', 'untranslated-contents' ) ) ) { + wp_safe_redirect( sanitize_url( $this->get_step_link( 'untranslated-contents' ) ) ); + exit; + } + + // Call the handler of the step for going to the next step. + // Be careful nonce verification with check_admin_referer must be done in each handler. + if ( ! empty( $_POST['save_step'] ) && isset( $this->steps[ $this->current_step ]['handler'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + call_user_func( $this->steps[ $this->current_step ]['handler'] ); + } + + $this->display_wizard_page(); + // Ensure nothing is done after including the page. + exit; + } + + /** + * Adds some admin screens where to display the wizard notice + * + * @since 2.7 + * + * @param bool $can_display_notice Whether the notice can be displayed. + * @param string $notice The notice name. + * @return bool + */ + public function can_display_notice( $can_display_notice, $notice ) { + if ( ! $can_display_notice && 'wizard' === $notice ) { + $screen = get_current_screen(); + $can_display_notice = ! empty( $screen ) && in_array( + $screen->base, + array( + 'edit', + 'upload', + 'options-general', + ) + ); + } + return $can_display_notice; + } + + /** + * Return html code of the wizard notice + * + * @since 2.7 + * + * @return string + */ + public function wizard_notice() { + ob_start(); + include __DIR__ . '/html-wizard-notice.php'; + return ob_get_clean(); + } + + /** + * Display the wizard page + * + * @since 2.7 + * + * @return void + */ + public function display_wizard_page() { + set_current_screen( 'pll-wizard' ); + do_action( 'admin_enqueue_scripts' ); + $steps = $this->steps; + $current_step = $this->current_step; + $styles = $this->styles; + include __DIR__ . '/view-wizard-page.php'; + } + + /** + * Enqueue scripts and styles for the wizard + * + * @since 2.7 + * + * @return void + */ + public function enqueue_scripts() { + wp_enqueue_style( 'polylang_admin', plugins_url( '/css/build/admin' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array(), POLYLANG_VERSION ); + wp_enqueue_style( 'pll-wizard', plugins_url( '/css/build/wizard' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common', 'forms' ), POLYLANG_VERSION ); + + $this->styles = array( 'polylang_admin', 'pll-wizard' ); + } + + /** + * Get the suffix to enqueue non minified files in a Debug context + * + * @since 2.7 + * + * @return string Empty when SCRIPT_DEBUG equal to true + * otherwise .min + */ + public function get_suffix() { + return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + } + + /** + * Get the URL for the step's screen. + * + * @since 2.7 + * + * @param string $step slug (default: current step). + * @return string URL for the step if it exists. + * Empty string on failure. + */ + public function get_step_link( $step = '' ) { + if ( ! $step ) { + $step = $this->current_step; + } + + $keys = array_keys( $this->steps ); + + $step_index = array_search( $step, $keys, true ); + if ( false === $step_index ) { + return ''; + } + + return add_query_arg( 'step', $keys[ $step_index ], remove_query_arg( 'activate_error' ) ); + } + + /** + * Get the URL for the next step's screen. + * + * @since 2.7 + * + * @param string $step slug (default: current step). + * @return string URL for next step if a next step exists. + * Admin URL if it's the last step. + * Empty string on failure. + */ + public function get_next_step_link( $step = '' ) { + if ( ! $step ) { + $step = $this->current_step; + } + + $keys = array_keys( $this->steps ); + if ( end( $keys ) === $step ) { + return admin_url(); + } + + $step_index = array_search( $step, $keys, true ); + if ( false === $step_index ) { + return ''; + } + + return add_query_arg( 'step', $keys[ $step_index + 1 ], remove_query_arg( 'activate_error' ) ); + } + + /** + * Add licenses step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_licenses( $steps ) { + // Add ajax action on deactivate button in licenses step. + add_action( 'wp_ajax_pll_deactivate_license', array( $this, 'deactivate_license' ) ); + + // Be careful `settings` script is enqueued here without dependency except jquery because only code useful for deactivate license button is needed. + // To be really loaded the script needs to be passed to the `$steps['licenses']['scripts']` array below with the same handle than in `wp_enqueue_script()`. + wp_enqueue_script( 'pll_settings', plugins_url( '/js/build/settings' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_settings', 'pll_settings', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + + if ( $this->is_licenses_step_displayable() ) { + $steps['licenses'] = array( + 'name' => esc_html__( 'Licenses', 'polylang' ), + 'view' => array( $this, 'display_step_licenses' ), + 'handler' => array( $this, 'save_step_licenses' ), + 'scripts' => array( 'pll_settings' ), // Polylang admin script used by deactivate license button. + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the languages step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_licenses() { + include __DIR__ . '/view-wizard-step-licenses.php'; + } + + /** + * Execute the languages step + * + * @since 2.7 + * + * @return void + */ + public function save_step_licenses() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $redirect = $this->get_next_step_link(); + $licenses = apply_filters( 'pll_settings_licenses', array() ); + + foreach ( $licenses as $license ) { + if ( ! empty( $_POST['licenses'][ $license->id ] ) ) { + $updated_license = $license->activate_license( sanitize_key( $_POST['licenses'][ $license->id ] ) ); + if ( ! empty( $updated_license->license_data ) && false === $updated_license->license_data->success ) { + // Stay on this step with an error. + $redirect = add_query_arg( + array( + 'step' => $this->current_step, + 'activate_error' => 'i18n_license_key_error', + ) + ); + } + } + } + + wp_safe_redirect( sanitize_url( $redirect ) ); + exit; + } + + /** + * Ajax method to deactivate a license + * + * @since 2.7 + * + * @return void + */ + public function deactivate_license() { + check_ajax_referer( 'pll-wizard', '_pll_nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( ! isset( $_POST['id'] ) ) { + wp_die( 0 ); + } + + $id = substr( sanitize_text_field( wp_unslash( $_POST['id'] ) ), 11 ); + $licenses = apply_filters( 'pll_settings_licenses', array() ); + $license = $licenses[ $id ]; + $license->deactivate_license(); + + wp_send_json( + array( + 'id' => $id, + 'html' => $license->get_form_field(), + ) + ); + } + + /** + * Add languages step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_languages( $steps ) { + wp_deregister_script( 'pll_settings' ); // Deregister after the licenses step enqueue to update jquery-ui-selectmenu dependency. + // The wp-ajax-response and postbox dependencies is useless in wizard steps especially postbox which triggers a javascript error otherwise. + // To be really loaded the script needs to be passed to the `$steps['languages']['scripts']` array below with the same handle than in `wp_enqueue_script()`. + wp_enqueue_script( 'pll_settings', plugins_url( '/js/build/settings' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-selectmenu' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_settings', 'pll_settings', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + wp_register_script( 'pll-wizard-languages', plugins_url( '/js/build/languages-step' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-dialog' ), POLYLANG_VERSION, true ); + wp_localize_script( + 'pll-wizard-languages', + 'pll_wizard_params', + array( + 'i18n_no_language_selected' => esc_html__( 'You need to select a language to be added.', 'polylang' ), + 'i18n_language_already_added' => esc_html__( 'You already added this language.', 'polylang' ), + 'i18n_no_language_added' => esc_html__( 'You need to add at least one language.', 'polylang' ), + 'i18n_add_language_needed' => esc_html__( 'You selected a language, however, to be able to continue, you need to add it.', 'polylang' ), + 'i18n_pll_add_language' => esc_html__( 'Impossible to add the language.', 'polylang' ), + 'i18n_pll_invalid_locale' => esc_html__( 'Enter a valid WordPress locale', 'polylang' ), + 'i18n_pll_invalid_slug' => esc_html__( 'The language code contains invalid characters', 'polylang' ), + 'i18n_pll_non_unique_slug' => esc_html__( 'The language code must be unique', 'polylang' ), + 'i18n_pll_invalid_name' => esc_html__( 'The language must have a name', 'polylang' ), + 'i18n_pll_invalid_flag' => esc_html__( 'The flag does not exist', 'polylang' ), + 'i18n_dialog_title' => esc_html__( "A language wasn't added.", 'polylang' ), + 'i18n_dialog_yes_button' => esc_html__( 'Yes', 'polylang' ), + 'i18n_dialog_no_button' => esc_html__( 'No', 'polylang' ), + 'i18n_dialog_ignore_button' => esc_html__( 'Ignore', 'polylang' ), + 'i18n_remove_language_icon' => esc_html__( 'Remove this language', 'polylang' ), + ) + ); + wp_enqueue_script( 'pll-wizard-languages' ); + wp_enqueue_style( 'pll-wizard-selectmenu', plugins_url( '/css/build/selectmenu' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common', 'wp-jquery-ui-dialog' ), POLYLANG_VERSION ); + $steps['languages'] = array( + 'name' => esc_html__( 'Languages', 'polylang' ), + 'view' => array( $this, 'display_step_languages' ), + 'handler' => array( $this, 'save_step_languages' ), + 'scripts' => array( 'pll-wizard-languages', 'pll_settings' ), + 'styles' => array( 'pll-wizard-selectmenu' ), + ); + return $steps; + } + + /** + * Display the languages step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_languages() { + $model = $this->model; + include __DIR__ . '/view-wizard-step-languages.php'; + } + + /** + * Execute the languages step + * + * @since 2.7 + * + * @return void + */ + public function save_step_languages() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $all_languages = include POLYLANG_DIR . '/settings/languages.php'; + $languages = isset( $_POST['languages'] ) && is_array( $_POST['languages'] ) ? array_map( 'sanitize_locale_name', $_POST['languages'] ) : false; + $saved_languages = array(); + + // If there is no language added or defined. + if ( empty( $languages ) && ! $this->model->has_languages() ) { + // Stay on this step with an error. + wp_safe_redirect( + sanitize_url( + add_query_arg( + array( + 'step' => $this->current_step, + 'activate_error' => 'i18n_no_language_added', + ) + ) + ) + ); + exit; + } + + // Otherwise process the languages to add or skip the step if no language has been added. + if ( ! empty( $languages ) ) { + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + // Remove duplicate values. + $languages = array_unique( $languages ); + // For each language add it in Polylang settings. + foreach ( $languages as $locale ) { + $saved_languages = $all_languages[ $locale ]; + + $saved_languages['slug'] = $saved_languages['code']; + $saved_languages['rtl'] = (int) ( 'rtl' === $saved_languages['dir'] ); + $saved_languages['term_group'] = 0; // Default term_group. + + $language_added = $this->model->add_language( $saved_languages ); + + if ( $language_added instanceof WP_Error && array_key_exists( 'pll_non_unique_slug', $language_added->errors ) ) { + // Get the slug from the locale : lowercase and dash instead of underscore. + $saved_languages['slug'] = strtolower( str_replace( '_', '-', $saved_languages['locale'] ) ); + $language_added = $this->model->add_language( $saved_languages ); + } + + if ( $language_added instanceof WP_Error ) { + // Stay on this step with an error. + $error_keys = array_keys( $language_added->errors ); + wp_safe_redirect( + sanitize_url( + add_query_arg( + array( + 'step' => $this->current_step, + 'activate_error' => 'i18n_' . reset( $error_keys ), + ) + ) + ) + ); + exit; + } + + if ( 'en_US' !== $locale && current_user_can( 'install_languages' ) ) { + wp_download_language_pack( $locale ); + } + } + } + wp_safe_redirect( sanitize_url( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add the media step to the wizard. + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_media( $steps ) { + $languages = $this->model->get_languages_list(); + + if ( $this->is_media_step_displayable( $languages ) ) { + $steps['media'] = array( + 'name' => esc_html__( 'Media', 'polylang' ), + 'view' => array( $this, 'display_step_media' ), + 'handler' => array( $this, 'save_step_media' ), + 'scripts' => array(), + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the media step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_media() { + $options = $this->options; + include __DIR__ . '/view-wizard-step-media.php'; + } + + /** + * Execute the media step + * + * @since 2.7 + * + * @return void + */ + public function save_step_media() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $media_support = isset( $_POST['media_support'] ) ? sanitize_key( $_POST['media_support'] ) === 'yes' : false; + + $this->options['media_support'] = $media_support; + + update_option( 'polylang', $this->options ); + + wp_safe_redirect( sanitize_url( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add untranslated contents step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_untranslated_contents( $steps ) { + if ( ! $this->model->has_languages() || $this->model->get_objects_with_no_lang( 1 ) ) { + // Even if `pll_settings` is already enqueued with the same dependencies by the languages step, it is interesting to keep that it's also useful for the untranslated-contents step. + // To be really loaded the script needs to be passed to the `$steps['untranslated-contents']['scripts']` array below with the same handle than in `wp_enqueue_script()`. + wp_enqueue_script( 'pll_settings', plugins_url( '/js/build/settings' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-selectmenu' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_settings', 'pll_settings', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + wp_enqueue_style( 'pll-wizard-selectmenu', plugins_url( '/css/build/selectmenu' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common' ), POLYLANG_VERSION ); + $steps['untranslated-contents'] = array( + 'name' => esc_html__( 'Content', 'polylang' ), + 'view' => array( $this, 'display_step_untranslated_contents' ), + 'handler' => array( $this, 'save_step_untranslated_contents' ), + 'scripts' => array( 'pll_settings' ), + 'styles' => array( 'pll-wizard-selectmenu' ), + ); + } + return $steps; + } + + /** + * Display the untranslated contents step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_untranslated_contents() { + $model = $this->model; + include __DIR__ . '/view-wizard-step-untranslated-contents.php'; + } + + /** + * Execute the untranslated contents step + * + * @since 2.7 + * + * @return void + */ + public function save_step_untranslated_contents() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $lang = ! empty( $_POST['language'] ) && is_string( $_POST['language'] ) ? sanitize_locale_name( $_POST['language'] ) : false; + + if ( empty( $lang ) ) { + $lang = $this->options['default_lang']; + } + + $language = $this->model->get_language( $lang ); + + if ( $language instanceof PLL_Language ) { + $this->model->set_language_in_mass( $language ); + } + + wp_safe_redirect( sanitize_url( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add home page step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_home_page( $steps ) { + $languages = $this->model->get_languages_list(); + $home_page_id = get_option( 'page_on_front' ); + + $translations = $this->model->post->get_translations( $home_page_id ); + + if ( $home_page_id > 0 && ( ! $languages || count( $languages ) === 1 || count( $translations ) !== count( $languages ) ) ) { + $steps['home-page'] = array( + 'name' => esc_html__( 'Homepage', 'polylang' ), + 'view' => array( $this, 'display_step_home_page' ), + 'handler' => array( $this, 'save_step_home_page' ), + 'scripts' => array(), + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the home page step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_home_page() { + $model = $this->model; + $languages = $model->languages->get_list(); + $home_page_id = get_option( 'page_on_front' ); + $home_page_id = is_numeric( $home_page_id ) ? (int) $home_page_id : 0; + $translations = $model->post->get_translations( $home_page_id ); + $home_page = $home_page_id > 0 ? get_post( $home_page_id ) : null; + $home_page_language = $model->post->get_language( $home_page_id ); + $untranslated_languages = array(); + + if ( empty( $home_page ) ) { + return; + } + + foreach ( $languages as $language ) { + if ( ! $model->post->get( $home_page_id, $language ) ) { + $untranslated_languages[] = $language; + } + } + include __DIR__ . '/view-wizard-step-home-page.php'; + } + + /** + * Execute the home page step + * + * @since 2.7 + * + * @return void + */ + public function save_step_home_page() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $default_language = $this->model->has_languages() ? $this->options['default_lang'] : null; + $home_page = isset( $_POST['home_page'] ) ? sanitize_key( $_POST['home_page'] ) : false; + $home_page_title = isset( $_POST['home_page_title'] ) ? sanitize_text_field( wp_unslash( $_POST['home_page_title'] ) ) : esc_html__( 'Homepage', 'polylang' ); + $home_page_language = isset( $_POST['home_page_language'] ) ? sanitize_key( $_POST['home_page_language'] ) : false; + + $untranslated_languages = isset( $_POST['untranslated_languages'] ) ? array_map( 'sanitize_key', $_POST['untranslated_languages'] ) : array(); + + call_user_func( + apply_filters( 'pll_wizard_create_home_page_translations', array( $this, 'create_home_page_translations' ) ), + $default_language, + $home_page, + $home_page_title, + $home_page_language, + $untranslated_languages + ); + + $this->model->clean_languages_cache(); + + wp_safe_redirect( sanitize_url( $this->get_next_step_link() ) ); + exit; + } + + /** + * Create home page translations for each language defined. + * + * @since 2.7 + * + * @param string $default_language Slug of the default language; null if no default language is defined. + * @param int $home_page Post ID of the home page if it's defined, false otherwise. + * @param string $home_page_title Home page title if it's defined, 'Homepage' otherwise. + * @param string $home_page_language Slug of the home page if it's defined, false otherwise. + * @param string[] $untranslated_languages Array of languages which needs to have a home page translated. + * @return void + */ + public function create_home_page_translations( $default_language, $home_page, $home_page_title, $home_page_language, $untranslated_languages ) { + $translations = $this->model->post->get_translations( $home_page ); + + foreach ( $untranslated_languages as $language ) { + $language_properties = $this->model->get_language( $language ); + $id = wp_insert_post( + array( + 'post_title' => $home_page_title . ' - ' . $language_properties->name, + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + $translations[ $language ] = $id; + pll_set_post_language( $id, $language ); + } + pll_save_post_translations( $translations ); + } + + /** + * Add last step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_last( $steps ) { + $steps['last'] = array( + 'name' => esc_html__( 'Ready!', 'polylang' ), + 'view' => array( $this, 'display_step_last' ), + 'handler' => array( $this, 'save_step_last' ), + 'scripts' => array(), + 'styles' => array(), + ); + return $steps; + } + + /** + * Display the last step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_last() { + // We ran the wizard once. So we can dismiss its notice. + PLL_Admin_Notices::dismiss( 'wizard' ); + include __DIR__ . '/view-wizard-step-last.php'; + } + + /** + * Execute the last step + * + * @since 2.7 + * + * @return void + */ + public function save_step_last() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + wp_safe_redirect( sanitize_url( $this->get_next_step_link() ) ); + exit; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/load.php b/wp-content/plugins/polylang/modules/wpml/load.php new file mode 100644 index 0000000..0cd81b0 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/load.php @@ -0,0 +1,17 @@ +model->has_languages() ) { + if ( ! defined( 'PLL_WPML_COMPAT' ) || PLL_WPML_COMPAT ) { + PLL_WPML_Compat::instance(); // WPML API. + PLL_WPML_Config::instance(); // wpml-config.xml. + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-api.php b/wp-content/plugins/polylang/modules/wpml/wpml-api.php new file mode 100644 index 0000000..84ef6e5 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-api.php @@ -0,0 +1,500 @@ + not applicable. + add_filter( 'wpml_current_language', 'pll_current_language', 10, 0 ); + add_filter( 'wpml_default_language', 'pll_default_language', 10, 0 ); + // wpml_add_language_selector => not implemented. + // wpml_footer_language_selector => not applicable. + add_action( 'wpml_add_language_form_field', array( $this, 'wpml_add_language_form_field' ) ); + add_filter( 'wpml_language_is_active', array( $this, 'wpml_language_is_active' ), 10, 2 ); + add_filter( 'wpml_is_rtl', array( $this, 'wpml_is_rtl' ) ); + // wpml_language_form_input_field => See wpml_add_language_form_field + // wpml_language_has_switched => See wpml_switch_language + add_filter( 'wpml_element_trid', array( $this, 'wpml_element_trid' ), 10, 3 ); + add_filter( 'wpml_get_element_translations', array( $this, 'wpml_get_element_translations' ), 10, 3 ); + // wpml_language_switcher => not implemented. + // wpml_browser_redirect_language_params => not implemented. + // wpml_enqueue_browser_redirect_language => not applicable. + // wpml_enqueued_browser_redirect_language => not applicable. + // wpml_encode_string => not applicable. + // wpml_decode_string => not applicable. + + /* + * Retrieving Language Information for Content. + */ + add_filter( 'wpml_post_language_details', 'wpml_get_language_information', 10, 2 ); + add_action( 'wpml_switch_language', array( self::class, 'wpml_switch_language' ), 10, 2 ); + add_filter( 'wpml_element_language_code', array( $this, 'wpml_element_language_code' ), 10, 2 ); + // wpml_element_language_details => not applicable. + + /* + * Retrieving Localized Content. + */ + add_filter( 'wpml_home_url', 'pll_home_url', 10, 0 ); + add_filter( 'wpml_element_link', 'icl_link_to_element', 10, 7 ); + add_filter( 'wpml_object_id', 'icl_object_id', 10, 4 ); + add_filter( 'wpml_translate_single_string', array( $this, 'wpml_translate_single_string' ), 10, 4 ); + // wpml_translate_string => not applicable. + // wpml_unfiltered_admin_string => not implemented. + add_filter( 'wpml_permalink', array( $this, 'wpml_permalink' ), 10, 2 ); + // wpml_elements_without_translations => not implemented. + add_filter( 'wpml_get_translated_slug', array( $this, 'wpml_get_translated_slug' ), 10, 3 ); + + /* + * Finding the Translation State of Content. + */ + // wpml_element_translation_type => not implemented. + add_filter( 'wpml_element_has_translations', array( $this, 'wpml_element_has_translations' ), 10, 3 ); + // wpml_master_post_from_duplicate => not applicable. + // wpml_post_duplicates => not applicable. + + /* + * Inserting Content. + */ + // wpml_admin_make_post_duplicates => not applicable. + // wpml_make_post_duplicates => not applicable. + add_action( 'wpml_register_single_string', 'icl_register_string', 10, 3 ); + // wpml_register_string => not applicable. + // wpml_register_string_packages => not applicable. + // wpml_delete_package_action => not applicable. + // wpml_show_package_language_ui => not applicable. + // wpml_set_element_language_details => not implemented. + // wpml_multilingual_options => not applicable. + + /* + * Miscellaneous + */ + // wpml_element_type => not applicable. + // wpml_setting => not applicable. + // wpml_sub_setting => not applicable. + // wpml_editor_cf_to_display => not applicable. + // wpml_tm_save_translation_cf => not implemented. + // wpml_tm_xliff_export_translated_cf => not applicable. + // wpml_tm_xliff_export_original_cf => not applicable. + // wpml_duplicate_generic_string => not applicable. + // wpml_translatable_user_meta_fields => not implemented. + // wpml_cross_domain_language_data => not applicable. + // wpml_get_cross_domain_language_data => not applicable. + // wpml_loaded => not applicable. + // wpml_st_loaded => not applicable. + // wpml_tm_loaded => not applicable. + // wpml_hide_management_column (3.4.1) => not applicable. + // wpml_ls_directories_to_scan => not applicable. + // wpml_ls_model_css_classes => not applicable. + // wpml_ls_model_language_css_classes => not applicable. + // wpml_tf_feedback_open_link => not applicable. + // wpml_sync_custom_field => not implemented. + // wpml_sync_all_custom_fields => not implemented. + // wpml_is_redirected => not implemented. + + /* + * Updating Content + */ + // wpml_set_translation_mode_for_post_type => not implemented. + + /* + * Undocumented + */ + add_filter( 'wpml_is_translated_post_type', array( $this, 'wpml_is_translated_post_type' ), 10, 2 ); + add_filter( 'wpml_is_translated_taxonomy', array( $this, 'wpml_is_translated_taxonomy' ), 10, 2 ); + } + + /** + * Get a list of the languages enabled for a site. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param array| string $args See arguments of icl_get_languages(). + * @return array Array of arrays per language. + */ + public function wpml_active_languages( $null, $args = '' ) { + return icl_get_languages( $args ); + } + + /** + * In WPML, get a language's native and translated name for display in a custom language switcher + * Since Polylang does not implement the translated name, always returns only the native name, + * so the 3rd, 4th and 5th parameters are not used. + * + * @since 2.2 + * + * @param mixed $null Not used. + * @param string $native_name The language native name. + * @return string + */ + public function wpml_display_language_names( $null, $native_name ) { + return $native_name; + } + + /** + * Returns an HTML hidden input field with name=”lang” and as value the current language. + * + * @since 2.0 + * + * @return void + */ + public function wpml_add_language_form_field() { + $lang = pll_current_language(); + + if ( empty( $lang ) ) { + return; + } + + $field = sprintf( '', esc_attr( $lang ) ); + $field = apply_filters( 'wpml_language_form_input_field', $field, $lang ); + echo $field; // phpcs:ignore WordPress.Security.EscapeOutput + } + + /** + * Find out if a specific language is enabled for the site. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param string $slug Language code. + * @return bool + */ + public function wpml_language_is_active( $null, $slug ) { + $language = PLL()->model->get_language( $slug ); + return ! empty( $language ) && $language->active; + } + + /** + * Find out whether the current language text direction is RTL or not. + * + * @since 2.0 + * + * @return bool + */ + public function wpml_is_rtl() { + return pll_current_language( 'is_rtl' ); + } + + /** + * Returns the id of the translation group of a translated element. + * + * @since 3.4 + * + * @param mixed $empty_value Not used. + * @param int $element_id The id of the item, post id for posts, term_taxonomy_id for terms. + * @param string $element_type Optional. The type of an element. + * @return int + */ + public function wpml_element_trid( $empty_value, $element_id, $element_type = 'post_post' ) { + if ( 0 === strpos( $element_type, 'tax_' ) ) { + $element = get_term_by( 'term_taxonomy_id', $element_id ); + if ( $element instanceof WP_Term ) { + $tr_term = PLL()->model->term->get_object_term( $element->term_id, 'term_translations' ); + } + } + + if ( 0 === strpos( $element_type, 'post_' ) ) { + $tr_term = PLL()->model->post->get_object_term( $element_id, 'post_translations' ); + } + + if ( isset( $tr_term ) && $tr_term instanceof WP_Term ) { + return $tr_term->term_id; + } + + return 0; + } + + /** + * Returns the element translations info using the ID of the translation group. + * + * @since 3.4 + * + * @param mixed $empty_value Not used. + * @param int $trid The ID of the translation group. + * @param string $element_type Optional. The type of an element. + * @return stdClass[] + */ + public function wpml_get_element_translations( $empty_value, $trid, $element_type = 'post_post' ) { + $return = array(); + + if ( 0 === strpos( $element_type, 'tax_' ) ) { + $translations = PLL()->model->term->get_translations_from_term_id( $trid ); + if ( empty( $translations ) ) { + return array(); + } + + $original = min( $translations ); // We suppose that the original is the first term created. + $source_lang = array_search( $original, $translations ); + + $args = array( + 'include' => $translations, + 'hide_empty' => false, + ); + $_terms = get_terms( $args ); + + if ( ! is_array( $_terms ) ) { + return array(); + } + + $terms = array(); + foreach ( $_terms as $term ) { + $terms[ $term->term_id ] = $term; + } + + foreach ( $translations as $lang => $term_id ) { + if ( empty( $terms[ $term_id ] ) ) { + continue; + } + + /* + * It seems that WPML fills the `instances` property with the total number of posts + * related to this term, while `WP_Term::$count` includes only *published* posts. + * We intentionnally accept this difference to avoid extra DB queries. + */ + $return[ $lang ] = (object) array( + 'translation_id' => '0', // We have nothing equivalent. + 'language_code' => $lang, + 'element_id' => (string) $terms[ $term_id ]->term_taxonomy_id, + 'source_language_code' => $source_lang === $lang ? null : $source_lang, + 'element_type' => $element_type, + 'original' => $original === $term_id ? '1' : '0', + 'name' => $terms[ $term_id ]->name, + 'term_id' => (string) $term_id, + 'instances' => (string) $terms[ $term_id ]->count, + ); + } + } + + if ( 0 === strpos( $element_type, 'post_' ) ) { + $translations = PLL()->model->post->get_translations_from_term_id( $trid ); + if ( empty( $translations ) ) { + return array(); + } + + $original = min( $translations ); // We suppose that the original is the first post created. + $source_lang = array_search( $original, $translations ); + + $args = array( + 'post__in' => $translations, + 'no_paging' => true, + 'posts_per_page' => -1, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'lang' => '', + ); + $_posts = get_posts( $args ); + + $posts = array(); + foreach ( $_posts as $post ) { + $posts[ $post->ID ] = $post; + } + + foreach ( $translations as $lang => $post_id ) { + if ( empty( $posts[ $post_id ] ) ) { + continue; + } + + $return[ $lang ] = (object) array( + 'translation_id' => '0', // We have nothing equivalent. + 'language_code' => $lang, + 'element_id' => (string) $post_id, + 'source_language_code' => $source_lang === $lang ? null : $source_lang, + 'element_type' => $element_type, + 'original' => $original === $post_id ? '1' : '0', + 'post_title' => $posts[ $post_id ]->post_title, + 'post_status' => $posts[ $post_id ]->post_status, + ); + } + } + + return $return; + } + + /** + * Switches whole site to the given language or restores the language that was set when first calling this function. + * Unlike the WPML original action, it is not possible to set the current language and the cookie to different values. + * + * @since 2.7 + * + * @param null|string $lang Language code to switch into, restores the original language if null. + * @param bool|string $cookie Optionally also switches the cookie. + * @return void + */ + public static function wpml_switch_language( $lang = null, $cookie = false ) { + if ( null === self::$original_language ) { + self::$original_language = PLL()->curlang; + } + + if ( empty( $lang ) ) { + PLL()->curlang = self::$original_language; + } elseif ( 'all' === $lang ) { + PLL()->curlang = null; + } elseif ( in_array( $lang, pll_languages_list() ) ) { + PLL()->curlang = PLL()->model->get_language( $lang ); + } + + if ( $cookie && isset( PLL()->choose_lang ) ) { + PLL()->choose_lang->maybe_setcookie(); + } + + do_action( 'wpml_language_has_switched', $lang, $cookie, self::$original_language ); + } + + /** + * Get the language code for a translatable element. + * + * @since 2.0 + * + * @param mixed $language_code A 2-letter language code. + * @param array $args An array with two keys element_id => post_id or term_taxonomy_id, element_type => post type or taxonomy + * @return string|null + */ + public function wpml_element_language_code( $language_code, $args ) { + $type = $args['element_type']; + $id = $args['element_id']; + + if ( 'post' === $type || pll_is_translated_post_type( $type ) ) { + $language = pll_get_post_language( $id ); + return is_string( $language ) ? $language : null; + } + + if ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) { + $term = get_term_by( 'term_taxonomy_id', $id ); + if ( $term instanceof WP_Term ) { + $id = $term->term_id; + } + $language = pll_get_term_language( $id ); + return is_string( $language ) ? $language : null; + } + + return null; + } + + /** + * Translates a string. + * + * @since 2.0 + * + * @param string $string The string's original value. + * @param string $context The string's registered context. + * @param string $name The string's registered name. + * @param null|string $lang Optional, return the translation in this language, defaults to current language. + * @return string The translated string. + */ + public function wpml_translate_single_string( $string, $context, $name, $lang = null ) { + $has_translation = null; // Passed by reference. + return icl_translate( $context, $name, $string, false, $has_translation, $lang ); + } + + /** + * Converts a permalink to a language specific permalink. + * + * @since 2.2 + * + * @param string $url The url to filter. + * @param null|string $lang Language code, optional, defaults to the current language. + * @return string + */ + public function wpml_permalink( $url, $lang = '' ) { + $lang = PLL()->model->get_language( $lang ); + + if ( empty( $lang ) && ! empty( PLL()->curlang ) ) { + $lang = PLL()->curlang; + } + + return empty( $lang ) ? $url : PLL()->links_model->switch_language_in_link( $url, $lang ); + } + + /** + * Translates a post type slug. + * + * @since 2.2 + * + * @param string $slug Post type slug. + * @param string $post_type Post type name. + * @param string $lang Optional language code (defaults to current language). + * @return string + */ + public function wpml_get_translated_slug( $slug, $post_type, $lang = null ) { + if ( isset( PLL()->translate_slugs ) ) { + if ( empty( $lang ) ) { + $lang = pll_current_language(); + } + + $slug = PLL()->translate_slugs->slugs_model->get_translated_slug( $post_type, $lang ); + } + return $slug; + } + + /** + * Find out whether a post type or a taxonomy term is translated. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param int $id The post_id or term_id. + * @param string $type The post type or taxonomy. + * @return bool + */ + public function wpml_element_has_translations( $null, $id, $type ) { + if ( 'post' === $type || pll_is_translated_post_type( $type ) ) { + return count( pll_get_post_translations( $id ) ) > 1; + } elseif ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) { + return count( pll_get_term_translations( $id ) ) > 1; + } + + return false; + } + + /** + * Returns true if languages and translations are managed for this post type. + * + * @since 3.4 + * + * @param mixed $value Not used. + * @param string $post_type The post type name. + * @return bool + */ + public function wpml_is_translated_post_type( $value, $post_type ) { + return pll_is_translated_post_type( $post_type ); + } + + /** + * Returns true if languages and translations are managed for this taxonomy. + * + * @since 3.4 + * + * @param mixed $value Not used. + * @param string $taxonomy The taxonomy name. + * @return bool + */ + public function wpml_is_translated_taxonomy( $value, $taxonomy ) { + return pll_is_translated_taxonomy( $taxonomy ); + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-compat.php b/wp-content/plugins/polylang/modules/wpml/wpml-compat.php new file mode 100644 index 0000000..8b29c1c --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-compat.php @@ -0,0 +1,193 @@ +api = new PLL_WPML_API(); + + $strings = get_option( 'polylang_wpml_strings' ); + + if ( is_array( $strings ) ) { + self::$strings = $strings; + add_filter( 'pll_get_strings', array( $this, 'get_strings' ) ); + } + + add_action( 'pll_language_defined', array( $this, 'define_constants' ) ); + add_action( 'pll_no_language_defined', array( $this, 'define_constants' ) ); + } + + /** + * Access to the single instance of the class + * + * @since 1.7 + * + * @return PLL_WPML_Compat + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Defines two WPML constants once the language has been defined + * The compatibility with WPML is not perfect on admin side as the constants are defined + * in 'setup_theme' by Polylang (based on user info) and 'plugins_loaded' by WPML (based on cookie). + * + * @since 0.9.5 + * + * @return void + */ + public function define_constants() { + if ( ! empty( PLL()->curlang ) ) { + if ( ! defined( 'ICL_LANGUAGE_CODE' ) ) { + define( 'ICL_LANGUAGE_CODE', PLL()->curlang->slug ); + } + + if ( ! defined( 'ICL_LANGUAGE_NAME' ) ) { + define( 'ICL_LANGUAGE_NAME', PLL()->curlang->name ); + } + } elseif ( ! PLL() instanceof PLL_Frontend ) { + if ( ! defined( 'ICL_LANGUAGE_CODE' ) ) { + define( 'ICL_LANGUAGE_CODE', 'all' ); + } + + if ( ! defined( 'ICL_LANGUAGE_NAME' ) ) { + define( 'ICL_LANGUAGE_NAME', '' ); + } + } + } + + /** + * Unlike pll_register_string, icl_register_string stores the string in database + * so we need to do the same as some plugins or themes may expect this. + * We use a serialized option to store these strings. + * + * @since 1.0.2 + * + * @param string|string[] $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @param string $string The string to register. + * @return void + */ + public function register_string( $context, $name, $string ) { + if ( ! $string || ! is_scalar( $string ) ) { + return; + } + + /* + * WPML accepts arrays as context and internally converts them to strings. + * See WPML_Register_String_Filter::truncate_name_and_context(). + * This possibility is used by Types. + */ + if ( is_array( $context ) ) { + $name = isset( $context['context'] ) ? $name . $context['context'] : $name; + $context = $context['domain'] ?? ''; + } + + // If a string has already been registered with the same name and context, let's replace it. + $exist_string = $this->get_string_by_context_and_name( $context, $name ); + if ( $exist_string && $exist_string !== $string ) { + $languages = PLL()->model->get_languages_list(); + + // Assign translations of the old string to the new string, except for the default language. + foreach ( $languages as $language ) { + if ( $language->is_default ) { + continue; + } + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + $mo->add_entry( $mo->make_entry( $string, $mo->translate( $exist_string ) ) ); + $mo->export_to_db( $language ); + } + $this->unregister_string( $context, $name ); + } + + // Registers the string if it does not exist yet (multiline as in WPML). + $to_register = array( 'context' => $context, 'name' => $name, 'string' => $string, 'multiline' => true, 'icl' => true ); + if ( ! in_array( $to_register, self::$strings ) ) { + $key = md5( "$context | $name" ); + self::$strings[ $key ] = $to_register; + update_option( 'polylang_wpml_strings', self::$strings ); + } + } + + /** + * Removes a string from the registered strings list + * + * @since 1.0.2 + * + * @param string $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @return void + */ + public function unregister_string( $context, $name ) { + $key = md5( "$context | $name" ); + if ( isset( self::$strings[ $key ] ) ) { + unset( self::$strings[ $key ] ); + update_option( 'polylang_wpml_strings', self::$strings ); + } + } + + /** + * Adds strings registered by icl_register_string to those registered by pll_register_string + * + * @since 1.0.2 + * + * @param array $strings existing registered strings + * @return array registered strings with added strings through WPML API + */ + public function get_strings( $strings ) { + return empty( self::$strings ) ? $strings : array_merge( $strings, self::$strings ); + } + + /** + * Get a registered string by its context and name + * + * @since 2.0 + * + * @param string $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @return string The registered string, empty if none was found. + */ + public function get_string_by_context_and_name( $context, $name ) { + $key = md5( "$context | $name" ); + return isset( self::$strings[ $key ] ) ? self::$strings[ $key ]['string'] : ''; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-config.php b/wp-content/plugins/polylang/modules/wpml/wpml-config.php new file mode 100644 index 0000000..072520e --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-config.php @@ -0,0 +1,1045 @@ + + */ +class PLL_WPML_Config { + /** + * Singleton instance + * + * @var PLL_WPML_Config|null + */ + protected static $instance; + + /** + * The content of all read xml files. + * + * @var SimpleXMLElement[] + */ + protected $xmls = array(); + + /** + * The list of xml file paths. + * + * @var string[]|null + * + * @phpstan-var array|null + */ + protected $files; + + /** + * List of rules to extract strings to translate from blocks. + * + * @var array + * + * @phpstan-var array{ + * xpath?: array>, + * key?: array> + * }|null + */ + protected $parsing_rules = null; + + /** + * Contains the list of path in `open_basedir`. + * + * @var string[]|null + */ + private $open_basedir_paths; + + /** + * Cache for parsed metas. + * + * @var array + * + * @phpstan-var array + */ + private $parsed_metas = array(); + + /** + * Constructor + * + * @since 1.0 + */ + public function __construct() { + if ( extension_loaded( 'simplexml' ) ) { + $this->init(); + } + } + + /** + * Access to the single instance of the class + * + * @since 1.7 + * + * @return PLL_WPML_Config + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Finds the wpml-config.xml files to parse and setup filters + * + * @since 1.0 + * + * @return void + */ + public function init() { + $this->xmls = array(); + $files = $this->get_files(); + + if ( empty( $files ) ) { + return; + } + + if ( ! extension_loaded( 'simplexml' ) ) { + return; + } + + // Read all files. + foreach ( $files as $context => $file ) { + $xml = simplexml_load_file( $file ); + if ( false !== $xml ) { + $this->xmls[ $context ] = $xml; + } + } + + if ( empty( $this->xmls ) ) { + return; + } + + add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 20, 2 ); + add_filter( 'pll_copy_term_metas', array( $this, 'copy_term_metas' ), 20, 2 ); + add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 ); + add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 ); + + // Export. + add_filter( 'pll_post_metas_to_export', array( $this, 'post_metas_to_export' ) ); + add_filter( 'pll_term_metas_to_export', array( $this, 'term_metas_to_export' ) ); + add_filter( 'pll_post_meta_encodings', array( $this, 'add_post_meta_encodings' ), 20 ); + add_filter( 'pll_term_meta_encodings', array( $this, 'add_term_meta_encodings' ), 20 ); + add_filter( 'pll_blocks_xpath_rules', array( $this, 'translate_blocks' ) ); + add_filter( 'pll_blocks_rules_for_attributes', array( $this, 'translate_blocks_attributes' ) ); + + $matcher = new PLL_Format_Util(); + + foreach ( $this->xmls as $context => $xml ) { + $keys = $xml->xpath( 'admin-texts/key' ); + + if ( ! is_array( $keys ) ) { + continue; + } + + foreach ( $keys as $key ) { + $name = $this->get_field_attribute( $key, 'name' ); + + if ( ! $matcher->is_format( $name ) ) { + $this->register_or_translate_option( $context, $name, $key ); + continue; + } + + $names = $matcher->filter_list( (array) wp_load_alloptions(), $name ); + + foreach ( $names as $_name => $_val ) { + $this->register_or_translate_option( $context, $_name, $key ); + } + } + } + } + + /** + * Returns all wpml-config.xml files in MU plugins, plugins, theme, child theme, and Polylang custom directory. + * + * @since 3.1 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + public function get_files() { + if ( is_array( $this->files ) ) { + return $this->files; + } + + $this->files = array_merge( + // Plugins. + $this->get_plugin_files(), + // Theme and child theme. + $this->get_theme_files(), + // MU Plugins. + $this->get_mu_plugin_files(), + // Custom. + $this->get_custom_files() + ); + + return $this->files; + } + + /** + * Adds post metas to the list of metas to copy when creating a new translation. + * + * @since 1.0 + * + * @param string[] $metas The list of post metas to copy or synchronize. + * @param bool $sync True for sync, false for copy. + * @return string[] The list of post metas to copy or synchronize. + * + * @phpstan-param array $metas + */ + public function copy_post_metas( $metas, $sync ) { + return $this->filter_metas_to_copy( (array) $metas, 'custom-fields/custom-field', (bool) $sync ); + } + + /** + * Adds term metas to the list of metas to copy when creating a new translation. + * + * @since 2.6 + * + * @param string[] $metas The list of term metas to copy or synchronize. + * @param bool $sync True for sync, false for copy. + * @return string[] The list of term metas to copy or synchronize. + * + * @phpstan-param array $metas + */ + public function copy_term_metas( $metas, $sync ) { + return $this->filter_metas_to_copy( (array) $metas, 'custom-term-fields/custom-term-field', (bool) $sync ); + } + + /** + * Adds post meta keys to export. + * + * @since 3.3 + * @see PLL_Export_Metas + * + * @param array $keys { + * A recursive array containing nested meta sub-keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => array( + * 'sub_key_to_translate_1' => 1, + * 'sub_key_to_translate_2' => array( + * 'sub_sub_key_to_translate_1' => 1, + * ), + * ), + * ) + * } + * @return array + * + * @phpstan-param array $keys + * @phpstan-return array + */ + public function post_metas_to_export( $keys ) { + // Add keys that have the `action` attribute set to `translate`. + $keys = $this->add_metas_to_export( (array) $keys, 'custom-fields/custom-field' ); + + // Deal with sub-field translations. + foreach ( $this->xmls as $xml ) { + $fields = $xml->xpath( 'custom-fields-texts/key' ); + + if ( ! is_array( $fields ) ) { + // No 'custom-fields-texts' nodes. + continue; + } + + foreach ( $fields as $field ) { + $name = $this->get_field_attribute( $field, 'name' ); + + if ( '' === $name ) { + // Wrong configuration: empty `name` attribute (meta name). + continue; + } + + if ( ! array_key_exists( $name, $keys ) ) { + // Wrong configuration: the field is not in `custom-fields/custom-field`. + continue; + } + + $keys = $this->xml_to_array( $field, $keys, 1 ); + } + } + + return $keys; + } + + /** + * Adds term meta keys to export. + * Note: sub-key translations are not currently supported by WPML. + * + * @since 3.3 + * @see PLL_Export_Metas + * + * @param array $keys { + * An array containing meta keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => 1, + * ) + * } + * @return array + * + * @phpstan-param array $keys + * @phpstan-return array + */ + public function term_metas_to_export( $keys ) { + // Add keys that have the `action` attribute set to `translate`. + return $this->add_metas_to_export( (array) $keys, 'custom-term-fields/custom-term-field' ); + } + + /** + * Specifies the encoding for post metas. + * + * @since 3.6 + * + * @param string[] $metas An array containing meta names as array keys, and their encoding as array values. + * @return string[] + * + * @phpstan-param array $metas + */ + public function add_post_meta_encodings( $metas ) { + return $this->add_metas_encodings( (array) $metas, 'custom-fields/custom-field' ); + } + + /** + * Specifies the encoding for term metas. + * + * @since 3.6 + * + * @param string[] $metas An array containing meta names as array keys, and their encoding as array values. + * @return string[] + * + * @phpstan-param array $metas + */ + public function add_term_meta_encodings( $metas ) { + return $this->add_metas_encodings( (array) $metas, 'custom-term-fields/custom-term-field' ); + } + + /** + * Language and translation management for custom post types. + * + * @since 1.0 + * + * @param string[] $types The list of post type names for which Polylang manages language and translations. + * @param bool $hide True when displaying the list in Polylang settings. + * @return string[] The list of post type names for which Polylang manages language and translations. + */ + public function translate_types( $types, $hide ) { + foreach ( $this->xmls as $xml ) { + $pts = $xml->xpath( 'custom-types/custom-type' ); + + if ( ! is_array( $pts ) ) { + continue; + } + + foreach ( $pts as $pt ) { + $translate = $this->get_field_attribute( $pt, 'translate' ); + + if ( '1' === $translate && ! $hide ) { + $types[ (string) $pt ] = (string) $pt; + } else { + unset( $types[ (string) $pt ] ); // The theme/plugin author decided what to do with the post type so don't allow the user to change this + } + } + } + + return $types; + } + + /** + * Language and translation management for custom taxonomies. + * + * @since 1.0 + * + * @param string[] $taxonomies The list of taxonomy names for which Polylang manages language and translations. + * @param bool $hide True when displaying the list in Polylang settings. + * @return string[] The list of taxonomy names for which Polylang manages language and translations. + */ + public function translate_taxonomies( $taxonomies, $hide ) { + foreach ( $this->xmls as $xml ) { + $taxos = $xml->xpath( 'taxonomies/taxonomy' ); + + if ( ! is_array( $taxos ) ) { + continue; + } + + foreach ( $taxos as $tax ) { + $translate = $this->get_field_attribute( $tax, 'translate' ); + + if ( '1' === $translate && ! $hide ) { + $taxonomies[ (string) $tax ] = (string) $tax; + } else { + unset( $taxonomies[ (string) $tax ] ); // the theme/plugin author decided what to do with the taxonomy so don't allow the user to change this + } + } + } + + return $taxonomies; + } + + /** + * Translation management for strings in blocks content. + * + * @since 3.3 + * + * @param string[][] $parsing_rules Rules as Xpath expressions to evaluate in the blocks content. + * @return string[][] Rules completed with ones from wpml-config file. + * + * @phpstan-param array> $parsing_rules + * @phpstan-return array> + */ + public function translate_blocks( $parsing_rules ) { + return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'xpath' ) ); + } + + /** + * Translation management for blocks attributes. + * + * @since 3.3 + * @since 3.6 Format changed from `array` to `array`. + * + * @param array $parsing_rules Rules for blocks attributes to translate. + * @return array Rules completed with ones from wpml-config file. + * + * @phpstan-param array $parsing_rules + * @phpstan-return array + */ + public function translate_blocks_attributes( $parsing_rules ) { + return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'key' ) ); + } + + /** + * Returns rules to extract translatable strings from blocks. + * + * @since 3.3 + * + * @param string $rule_tag Tag name to extract. + * @return string[][] The rules. + * + * @phpstan-param 'xpath'|'key' $rule_tag + * @phpstan-return ( + * $rule_tag is 'xpath' ? array> : array> + * ) + */ + protected function get_blocks_parsing_rules( $rule_tag ) { + + if ( null === $this->parsing_rules ) { + $this->parsing_rules = $this->extract_blocks_parsing_rules(); + } + + return $this->parsing_rules[ $rule_tag ] ?? array(); + } + + /** + * Extract all rules from WPML config file to translate strings for blocks. + * + * @since 3.3 + * + * @return string[][][] Rules completed with ones from wpml-config file. + * + * @phpstan-return array{ + * xpath?: array>, + * key?: array> + * } + */ + protected function extract_blocks_parsing_rules() { + $parsing_rules = array(); + + foreach ( $this->xmls as $xml ) { + $blocks = $xml->xpath( 'gutenberg-blocks/gutenberg-block' ); + + if ( ! is_array( $blocks ) ) { + continue; + } + + foreach ( $blocks as $block ) { + $translate = $this->get_field_attribute( $block, 'translate' ); + + if ( '1' !== $translate ) { + continue; + } + + $block_name = $this->get_field_attribute( $block, 'type' ); + + if ( '' === $block_name ) { + continue; + } + + foreach ( $block->children() as $child ) { + $rule = ''; + $child_tag = $child->getName(); + + switch ( $child_tag ) { + case 'xpath': + $rule = trim( (string) $child ); + + if ( '' !== $rule ) { + $parsing_rules['xpath'][ $block_name ][] = $rule; + } + break; + + case 'key': + $rule = $this->get_field_attributes( $child ); + + if ( empty( $rule ) ) { + break; + } + + if ( isset( $parsing_rules['key'][ $block_name ] ) ) { + $parsing_rules['key'][ $block_name ] = $this->array_merge_recursive( $parsing_rules['key'][ $block_name ], $rule ); + } else { + $parsing_rules['key'][ $block_name ] = $rule; + } + break; + } + } + } + } + + return $parsing_rules; + } + + /** + * Merges two arrays recursively. + * Unlike `array_merge_recursive()`, this method doesn't change the type of the values. + * + * @since 3.6 + * + * @param array $array1 Array to merge into. + * @param array $array2 Array to merge. + * @return array + */ + protected function array_merge_recursive( array $array1, array $array2 ): array { + foreach ( $array2 as $key => $value ) { + if ( is_array( $value ) && isset( $array1[ $key ] ) && is_array( $array1[ $key ] ) ) { + $array1[ $key ] = $this->array_merge_recursive( $array1[ $key ], $value ); + } else { + $array1[ $key ] = $value; + } + } + + return $array1; + } + + /** + * Registers or translates the strings for an option + * + * @since 2.8 + * + * @param string $context The group in which the strings will be registered. + * @param string $name Option name. + * @param SimpleXMLElement $key XML node. + * @return void + */ + protected function register_or_translate_option( $context, $name, $key ) { + $option_keys = $this->xml_to_array( $key ); + new PLL_Translate_Option( $name, reset( $option_keys ), array( 'context' => $context ) ); + } + + /** + * Recursively transforms xml nodes to an array, ready for PLL_Translate_Option. + * + * @since 2.9 + * @since 3.3 Type-hinted the parameters `$key` and `$arr`. + * @since 3.3 `$arr` is not passed by reference anymore. + * @since 3.3 Added the parameter `$fill_value`. + * + * @param SimpleXMLElement $key XML node. + * @param array $arr Array of option keys to translate. + * @param mixed $fill_value Value to use when filling entries. Default is true. + * @return array + */ + protected function xml_to_array( SimpleXMLElement $key, array $arr = array(), $fill_value = true ) { + $name = $this->get_field_attribute( $key, 'name' ); + + if ( '' === $name ) { + return $arr; + } + + $children = $key->children(); + + if ( count( $children ) ) { + foreach ( $children as $child ) { + if ( ! isset( $arr[ $name ] ) || ! is_array( $arr[ $name ] ) ) { + $arr[ $name ] = array(); + } + + $arr[ $name ] = $this->xml_to_array( $child, $arr[ $name ], $fill_value ); + } + } else { + $arr[ $name ] = $fill_value; // Multiline as in WPML. + } + + return $arr; + } + + /** + * Get the value of an attribute. + * + * @since 3.3 + * + * @param SimpleXMLElement $field A XML node. + * @param string $attribute_name Node of the attribute. + * @return string + */ + private function get_field_attribute( SimpleXMLElement $field, $attribute_name ) { + $attributes = $field->attributes(); + + if ( empty( $attributes ) || ! isset( $attributes[ $attribute_name ] ) ) { + return ''; + } + + return trim( (string) $attributes[ $attribute_name ] ); + } + + /** + * Gets attributes values recursively. + * + * @since 3.6 + * + * @param SimpleXMLElement $field A XML node. + * @return array An array of attributes. + * + * @phpstan-return array + */ + private function get_field_attributes( SimpleXMLElement $field ): array { + $name = $this->get_field_attribute( $field, 'name' ); + + if ( '' === $name ) { + return array(); + } + + $children = $field->children(); + + if ( 0 === $children->count() ) { + return array( $name => true ); + } + + $sub_attributes = array(); + + foreach ( $children as $child ) { + $sub = $this->get_field_attributes( $child ); + + if ( empty( $sub ) ) { + continue; + } + + $sub_attributes[ $name ] = array_merge( $sub_attributes[ $name ] ?? array(), $sub ); + } + + return $sub_attributes; + } + + /** + * Returns all wpml-config.xml files in MU plugins. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_mu_plugin_files() { + if ( ! is_readable( WPMU_PLUGIN_DIR ) || ! is_dir( WPMU_PLUGIN_DIR ) ) { + return array(); + } + + $files = array(); + + // Search for top level wpml-config.xml file. + $file_path = WPMU_PLUGIN_DIR . '/wpml-config.xml'; + + if ( is_readable( $file_path ) ) { + $files['mu-plugins'] = $file_path; + } + + // Search in proxy loaded MU plugins. + foreach ( new DirectoryIterator( WPMU_PLUGIN_DIR ) as $file_info ) { + if ( ! $this->is_dir( $file_info ) ) { + continue; + } + + $file_path = $file_info->getPathname() . '/wpml-config.xml'; + + if ( is_readable( $file_path ) ) { + $files[ 'mu-plugins/' . $file_info->getFilename() ] = $file_path; + } + } + + return $files; + } + + /** + * Returns all wpml-config.xml files in plugins. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_plugin_files() { + $files = array(); + $plugins = array(); + + if ( is_multisite() ) { + // Don't forget sitewide active plugins thanks to Reactorshop http://wordpress.org/support/topic/polylang-and-yoast-seo-plugin/page/2?replies=38#post-4801829. + $sitewide_plugins = get_site_option( 'active_sitewide_plugins', array() ); + + if ( ! empty( $sitewide_plugins ) && is_array( $sitewide_plugins ) ) { + $plugins = array_keys( $sitewide_plugins ); + } + } + + // By-site plugins. + $active_plugins = get_option( 'active_plugins', array() ); + + if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) { + $plugins = array_merge( $plugins, $active_plugins ); + } + + $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . '%s/wpml-config.xml'; + + foreach ( $plugins as $plugin ) { + if ( ! is_string( $plugin ) || '' === $plugin ) { + continue; + } + + $file_dir = dirname( $plugin ); + $file_path = sprintf( $plugin_path, $file_dir ); + + if ( is_readable( $file_path ) ) { + $files[ "plugins/{$file_dir}" ] = $file_path; + } + } + + return $files; + } + + /** + * Returns all wpml-config.xml files in theme and child theme. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_theme_files() { + $files = array(); + + // Theme. + $template_path = get_template_directory(); + $file_path = "{$template_path}/wpml-config.xml"; + + if ( is_readable( $file_path ) ) { + $files[ 'themes/' . get_template() ] = $file_path; + } + + // Child theme. + $stylesheet_path = get_stylesheet_directory(); + $file_path = "{$stylesheet_path}/wpml-config.xml"; + + if ( $stylesheet_path !== $template_path && is_readable( $file_path ) ) { + $files[ 'themes/' . get_stylesheet() ] = $file_path; + } + + return $files; + } + + /** + * Returns the wpml-config.xml file in Polylang custom directory. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_custom_files() { + $file_path = PLL_LOCAL_DIR . '/wpml-config.xml'; + + if ( ! is_readable( $file_path ) ) { + return array(); + } + + return array( + 'Polylang' => $file_path, + ); + } + + /** + * Tells if the given "file info" object represents a directory. + * This takes care of not triggering a `open_basedir` restriction error when the file symlinks a file that is not in + * `open_basedir`. + * + * @see https://wordpress.org/support/topic/fatal-error-open_basedir-restricton/ + * + * @since 3.5.1 + * + * @param DirectoryIterator $file_info A "file info" object that we know its path (but maybe not its real path) is + * in `open_basedir`. + * @return bool + */ + private function is_dir( DirectoryIterator $file_info ): bool { + if ( $file_info->isDot() ) { + return false; + } + + if ( $file_info->getPathname() === $file_info->getRealPath() ) { + // Not a symlink: not going to trigger a `open_basedir` restriction error. + return $file_info->isDir(); + } + + /* + * Symlink: make sure the file's real path is in `open_basedir` before checking it is a dir. + * Which means that the `open_basedir` check is done only for symlinked files. + */ + return $this->is_allowed_dir( $file_info->getRealPath() ) && $file_info->isDir(); + } + + /** + * Checks whether access to a given directory is allowed. + * This takes into account the PHP `open_basedir` restrictions, so that Polylang does not try to access directories + * it is not allowed to. + * + * Inspired by `WP_Automatic_Updater::is_allowed_dir()` and `wp-includes/ID3/getid3.php`. + * + * @since 3.5.1 + * + * @param string $dir The directory to check. + * @return bool True if access to the directory is allowed, false otherwise. + */ + private function is_allowed_dir( string $dir ): bool { + $dir = trim( $dir ); + + if ( '' === $dir ) { + return false; + } + + $open_basedir_paths = $this->get_open_basedir_paths(); + + if ( empty( $open_basedir_paths ) ) { + return true; + } + + $dir = $this->normalize_path( $dir ); + + foreach ( $open_basedir_paths as $path ) { + if ( str_starts_with( $dir, $path ) ) { + return true; + } + } + + return false; + } + + /** + * Returns the list of paths in `open_basedir`. The purpose is to compare a formatted path to this list. + * Note: all paths are suffixed by `DIRECTORY_SEPARATOR`, even paths to files. + * + * @since 3.5.1 + * + * @return string[] An array of formatted paths. + */ + private function get_open_basedir_paths(): array { + if ( is_array( $this->open_basedir_paths ) ) { + return $this->open_basedir_paths; + } + + $this->open_basedir_paths = array(); + $open_basedir = ini_get( 'open_basedir' ); // Can be `false` or an empty string. + + if ( empty( $open_basedir ) ) { + return $this->open_basedir_paths; + } + + $open_basedir_list = explode( PATH_SEPARATOR, $open_basedir ); + + foreach ( $open_basedir_list as $basedir ) { + $basedir = trim( $basedir ); + + if ( '' === $basedir ) { + continue; + } + + $this->open_basedir_paths[] = $this->normalize_path( $basedir ); + } + + $this->open_basedir_paths = array_unique( $this->open_basedir_paths ); + + return $this->open_basedir_paths; + } + + /** + * Formats a path for string comparison. + * 1. Slashes and back-slashes are replaced by `DIRECTORY_SEPARATOR`. + * 2. The path is suffixed by `DIRECTORY_SEPARATOR` (even non-directory elements). + * + * @since 3.5.1 + * + * @param string $path A file path. + * @return string + * + * @phpstan-param non-empty-string $path + * @phpstan-return non-empty-string + */ + private function normalize_path( string $path ): string { + $path = str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path ); + + if ( substr( $path, -1, 1 ) !== DIRECTORY_SEPARATOR ) { + $path .= DIRECTORY_SEPARATOR; + } + + return $path; + } + + /** + * Adds (or removes) meta names to the list of metas to copy or synchronize. + * + * @since 3.6 + * + * @param string[] $metas The list of meta names to copy or synchronize. + * @param string $xpath Xpath to the meta fields in the xml files. + * @param bool $sync Either sync is enabled or not. + * @return string[] + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + */ + private function filter_metas_to_copy( array $metas, string $xpath, bool $sync ): array { + $parsed_metas = $this->parse_xml_metas( $xpath ); + $metas_to_remove = array(); + + foreach ( $parsed_metas as $name => $parsed_meta ) { + if ( 'copy' === $parsed_meta['action'] || ( ! $sync && in_array( $parsed_meta['action'], array( 'translate', 'copy-once' ), true ) ) ) { + $metas[] = $name; + } else { + $metas_to_remove[] = $name; + } + } + + return array_diff( $metas, $metas_to_remove ); + } + + /** + * Adds meta keys to export. + * + * @since 3.6 + * + * @param array $metas { + * An array containing meta keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => array( ... ), + * ) + * } + * @param string $xpath Xpath to the meta fields in the xml files. + * @return array + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + * @phpstan-return array + */ + private function add_metas_to_export( array $metas, string $xpath ) { + $fields = $this->parse_xml_metas( $xpath ); + + foreach ( $fields as $name => $field ) { + if ( 'translate' === $field['action'] ) { + $metas[ $name ] = 1; + } + } + + return $metas; + } + + /** + * Adds encoding of metas. + * + * @since 3.6 + * + * @param string[] $metas The list of encodings for each metas. Meta names are array keys, encodings are array values. + * @param string $xpath Xpath to the meta fields in the xml files. + * @return string[] + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + */ + private function add_metas_encodings( array $metas, string $xpath ): array { + $parsed_metas = $this->parse_xml_metas( $xpath ); + + foreach ( $parsed_metas as $name => $parsed_meta ) { + if ( ! empty( $parsed_meta['encoding'] ) ) { + $metas[ $name ] = $parsed_meta['encoding']; + } + } + + return $metas; + } + + /** + * Parses all xml files for metas. + * Results are cached for each `$xpath`. + * + * @since 3.6 + * + * @param string $xpath Xpath to the meta fields in the xml files. + * @return array + * + * @phpstan-param non-falsy-string $xpath + * @phpstan-return ParsedMetas + */ + private function parse_xml_metas( string $xpath ): array { + if ( isset( $this->parsed_metas[ $xpath ] ) ) { + return $this->parsed_metas[ $xpath ]; + } + + $this->parsed_metas[ $xpath ] = array(); + + foreach ( $this->xmls as $xml ) { + $custom_fields = $xml->xpath( $xpath ); + + if ( ! is_array( $custom_fields ) ) { + continue; + } + + foreach ( $custom_fields as $custom_field ) { + $name = (string) $custom_field; + + if ( empty( $name ) ) { + continue; + } + + $data = array( + 'action' => $this->get_field_attribute( $custom_field, 'action' ), + 'encoding' => $this->get_field_attribute( $custom_field, 'encoding' ), + ); + + $data['encoding'] = 'json' === $data['encoding'] ? 'json' : ''; // Only JSON is supported for now. + + $this->parsed_metas[ $xpath ][ $name ] = $data; + } + } + + return $this->parsed_metas[ $xpath ]; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php b/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php new file mode 100644 index 0000000..4cb4b53 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php @@ -0,0 +1,435 @@ + whether to skip missing translation or not, 0 or 1, defaults to 0 + * orderby => 'id', 'code', 'name', defaults to 'id' + * order => 'ASC' or 'DESC', defaults to 'ASC' + * link_empty_to => link to use when the translation is missing {$lang} is replaced by the language code + * + * List of parameters returned per language: + * + * id => the language id + * active => whether this is the active language or no, 0 or 1 + * native_name => the language name + * missing => whether the translation is missing or not, 0 or 1 + * translated_name => empty, does not exist in Polylang + * language_code => the language code ( slug ) + * country_flag_url => the url of the flag + * url => the url of the translation + * + * @since 1.0 + * + * @param string|array $args optional + * @return array array of arrays per language + */ + function icl_get_languages( $args = '' ) { + $args = wp_parse_args( $args, array( 'skip_missing' => 0, 'orderby' => 'id', 'order' => 'ASC' ) ); + $orderby = ( isset( $args['orderby'] ) && 'code' == $args['orderby'] ) ? 'slug' : ( isset( $args['orderby'] ) && 'name' == $args['orderby'] ? 'name' : 'id' ); + $order = ( ! empty( $args['order'] ) && 'desc' == $args['order'] ) ? 'DESC' : 'ASC'; + + $arr = array(); + + // NB: When 'skip_missing' is false, WPML returns all languages even if there is no content + $languages = PLL()->model->get_languages_list( array( 'hide_empty' => $args['skip_missing'] ) ); + $languages = wp_list_sort( $languages, $orderby, $order ); // Since WP 4.7 + + foreach ( $languages as $lang ) { + // We can find a translation only on frontend once the global $wp_query object has been instantiated + if ( PLL()->links instanceof PLL_Frontend_Links && ! empty( $GLOBALS['wp_query'] ) ) { + $url = PLL()->links->get_translation_url( $lang ); + } + + // It seems that WPML does not bother of skip_missing parameter on admin side and before the $wp_query object has been filled + if ( empty( $url ) && ! empty( $args['skip_missing'] ) && ! is_admin() && did_action( 'parse_query' ) ) { + continue; + } + + $arr[ $lang->slug ] = array( + 'id' => $lang->term_id, + 'active' => isset( PLL()->curlang->slug ) && PLL()->curlang->slug == $lang->slug ? 1 : 0, + 'native_name' => $lang->name, + 'missing' => empty( $url ) ? 1 : 0, + 'translated_name' => '', // Does not exist in Polylang + 'language_code' => $lang->slug, + 'country_flag_url' => $lang->get_display_flag_url(), + 'url' => ! empty( $url ) ? $url : + ( empty( $args['link_empty_to'] ) ? $lang->get_home_url() : + str_replace( '{$lang}', $lang->slug, $args['link_empty_to'] ) ), + ); + } + + // Apply undocumented WPML filter + $arr = apply_filters( 'icl_ls_languages', $arr ); + + return $arr; + } +} + +if ( ! function_exists( 'icl_link_to_element' ) ) { + /** + * Used for creating language dependent links in themes + * + * @since 1.0 + * @since 2.0 add support for arguments 6 and 7 + * + * @param int $id object id + * @param string $type optional, post type or taxonomy name of the object, defaults to 'post' + * @param string $text optional, the link text. If not specified will produce the name of the element in the current language + * @param array $args optional, an array of arguments to add to the link, defaults to empty + * @param string $anchor optional, the anchor to add to the link, defaults to empty + * @param bool $echo optional, whether to echo the link, defaults to true + * @param bool $return_original_if_missing optional, whether to return a value if the translation is missing + * @return string a language dependent link + */ + function icl_link_to_element( $id, $type = 'post', $text = '', $args = array(), $anchor = '', $echo = true, $return_original_if_missing = true ) { + if ( 'tag' == $type ) { + $type = 'post_tag'; + } + + $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false ); + if ( $pll_type && ( $lang = pll_current_language() ) && ( $tr_id = PLL()->model->$pll_type->get_translation( $id, $lang ) ) && ( 'term' === $pll_type || PLL()->model->post->current_user_can_read( $tr_id ) ) ) { + $id = $tr_id; + } elseif ( ! $return_original_if_missing ) { + return ''; + } + + if ( post_type_exists( $type ) ) { + $link = get_permalink( $id ); + if ( empty( $text ) ) { + $text = get_the_title( $id ); + } + } elseif ( taxonomy_exists( $type ) ) { + $link = get_term_link( $id, $type ); + if ( empty( $text ) && ( $term = get_term( $id, $type ) ) && $term instanceof WP_Term ) { + $text = $term->name; + } + } + + if ( empty( $link ) || is_wp_error( $link ) ) { + return ''; + } + + if ( ! empty( $args ) ) { + $link .= ( false === strpos( $link, '?' ) ? '?' : '&' ) . http_build_query( $args ); + } + + if ( ! empty( $anchor ) ) { + $link .= '#' . $anchor; + } + + $link = sprintf( '%s', esc_url( $link ), esc_html( $text ) ); + + if ( $echo ) { + echo $link; // phpcs:ignore WordPress.Security.EscapeOutput + } + + return $link; + } +} + +if ( ! function_exists( 'icl_object_id' ) ) { + /** + * Returns an element’s ID in the current language or in another specified language. + * + * @since 0.9.5 + * + * @param int $element_id Object id. + * @param string $element_type Optional, post type or taxonomy name of the object, defaults to 'post'. + * @param bool $return_original_if_missing Optional, true if Polylang should return the original id if the translation is missing, defaults to false. + * @param string|null $ulanguage_code Optional, language code, defaults to the current language. + * @return int|null The object id of the translation, null if the translation is missing and $return_original_if_missing set to false. + */ + function icl_object_id( $element_id, $element_type = 'post', $return_original_if_missing = false, $ulanguage_code = null ) { + if ( empty( $element_id ) ) { + return null; + } + + $element_id = (int) $element_id; + + if ( 'any' === $element_type ) { + $element_type = get_post_type( $element_id ); + } + + if ( empty( $element_type ) ) { + return null; + } + + if ( empty( $ulanguage_code ) ) { + $ulanguage_code = pll_current_language(); + } + + if ( empty( $ulanguage_code ) ) { + return $return_original_if_missing ? $element_id : null; + } + + if ( 'nav_menu' === $element_type ) { + $tr_id = false; + $theme = get_option( 'stylesheet' ); + if ( isset( PLL()->options['nav_menus'][ $theme ] ) ) { + foreach ( PLL()->options['nav_menus'][ $theme ] as $menu ) { + if ( array_search( $element_id, $menu ) && ! empty( $menu[ $ulanguage_code ] ) ) { + $tr_id = $menu[ $ulanguage_code ]; + break; + } + } + } + } elseif ( pll_is_translated_post_type( $element_type ) ) { + $tr_id = PLL()->model->post->get_translation( $element_id, $ulanguage_code ); + } elseif ( pll_is_translated_taxonomy( $element_type ) ) { + $tr_id = PLL()->model->term->get_translation( $element_id, $ulanguage_code ); + } else { + return $element_id; // WPML doesn't honor $return_original_if_missing if the post type or taxonomy is not translated, @see {SitePress::get_object_id()}. + } + + if ( empty( $tr_id ) ) { + if ( $return_original_if_missing ) { + return $element_id; + } + + return null; + } + + return (int) $tr_id; + } +} + +if ( ! function_exists( 'wpml_object_id_filter' ) ) { + /** + * Undocumented alias of `icl_object_id` introduced in WPML 3.2, used by Yith WooCommerce compare + * + * @since 2.2.4 + * + * @param int $id object id + * @param string $type optional, post type or taxonomy name of the object, defaults to 'post' + * @param bool $return_original_if_missing optional, true if Polylang should return the original id if the translation is missing, defaults to false + * @param string $lang optional, language code, defaults to current language + * @return int|null the object id of the translation, null if the translation is missing and $return_original_if_missing set to false + */ + function wpml_object_id_filter( $id, $type = 'post', $return_original_if_missing = false, $lang = null ) { + return icl_object_id( $id, $type, $return_original_if_missing, $lang ); + } +} + +if ( ! function_exists( 'wpml_get_language_information' ) ) { + /** + * Undocumented function used by the theme Maya + * returns the post language + * + * @see https://wpml.org/forums/topic/canonical-urls-for-wpml-duplicated-posts/#post-52198 for the original WPML code + * + * @since 1.8 + * + * @param null $empty optional, not used + * @param int $post_id optional, post id, defaults to current post + * @return array|WP_Error + */ + function wpml_get_language_information( $empty = null, $post_id = null ) { + if ( null === $post_id ) { + $post_id = get_the_ID(); + } + if ( empty( $post_id ) ) { + return new WP_Error( 'missing_id', __( 'Missing post ID', 'polylang' ) ); + } + + $post = get_post( $post_id ); + if ( empty( $post ) ) { + // translators: Post id. + return new WP_Error( 'missing_post', sprintf( __( 'No such post for ID = %d', 'polylang' ), $post_id ) ); + } + + $lang = PLL()->model->post->get_language( $post_id ); + + // FIXME WPML may return a WP_Error object + return false === $lang ? array() : array( + 'language_code' => $lang->slug, + 'locale' => $lang->locale, + 'text_direction' => (bool) $lang->is_rtl, + 'display_name' => $lang->name, // Seems to be the post language name displayed in the current language, not a feature in Polylang + 'native_name' => $lang->name, + 'different_language' => pll_current_language() !== $lang->slug, + ); + } +} + +if ( ! function_exists( 'icl_register_string' ) ) { + /** + * Registers a string for translation in the "strings translation" panel + * + * The 4th and 5th parameters $allow_empty_value and $source_lang are not used by Polylang. + * + * @since 0.9.3 + * + * @param string $context the group in which the string is registered, defaults to 'polylang' + * @param string $name a unique name for the string + * @param string $string the string to register + * @return void + */ + function icl_register_string( $context, $name, $string ) { + PLL_WPML_Compat::instance()->register_string( $context, $name, $string ); + } +} + +if ( ! function_exists( 'icl_unregister_string' ) ) { + /** + * Removes a string from the "strings translation" panel + * + * @since 1.0.2 + * + * @param string $context the group in which the string is registered, defaults to 'polylang' + * @param string $name a unique name for the string + * @return void + */ + function icl_unregister_string( $context, $name ) { + PLL_WPML_Compat::instance()->unregister_string( $context, $name ); + } +} + +if ( ! function_exists( 'icl_t' ) ) { + /** + * Gets the translated value of a string ( previously registered with icl_register_string or pll_register_string ) + * + * @since 0.9.3 + * @since 1.9.2 argument 3 is optional + * @since 2.0 add support for arguments 4 to 6 + * + * @param string $context the group in which the string is registered + * @param string $name a unique name for the string + * @param string $string the string to translate, optional for strings registered with icl_register_string + * @param bool|null $has_translation optional, not supported in Polylang + * @param bool $bool optional, not used + * @param string|null $lang optional, return the translation in this language, defaults to current language + * @return string the translated string + */ + function icl_t( $context, $name, $string = '', &$has_translation = null, $bool = false, $lang = null ) { + return icl_translate( $context, $name, $string, false, $has_translation, $lang ); + } +} + +if ( ! function_exists( 'icl_translate' ) ) { + /** + * Undocumented function used by NextGen Gallery + * used in PLL_Plugins_Compat for Jetpack with only 3 arguments + * + * @since 1.0.2 + * @since 2.0 add support for arguments 5 and 6, strings are no more automatically registered + * + * @param string $context the group in which the string is registered + * @param string $name a unique name for the string + * @param string $string the string to translate, optional for strings registered with icl_register_string + * @param bool $bool optional, not used + * @param bool|null $has_translation optional, not supported in Polylang + * @param string|null $lang optional, return the translation in this language, defaults to current language + * @return string the translated string + */ + function icl_translate( $context, $name, $string = '', $bool = false, &$has_translation = null, $lang = null ) { + // FIXME WPML can automatically registers the string based on an option + if ( empty( $string ) ) { + $string = PLL_WPML_Compat::instance()->get_string_by_context_and_name( $context, $name ); + } + return empty( $lang ) ? pll__( $string ) : pll_translate_string( $string, $lang ); + } +} + +if ( ! function_exists( 'wpml_get_copied_fields_for_post_edit' ) ) { + /** + * Undocumented function used by Types + * FIXME: tested only with Types + * probably incomplete as Types locks the custom fields for a new post, but not when edited + * This is probably linked to the fact that WPML has always an original post in the default language and not Polylang :) + * + * @since 1.1.2 + * + * @return array + */ + function wpml_get_copied_fields_for_post_edit() { + if ( empty( $_GET['from_post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + return array(); + } + + $arr = array( 'original_post_id' => (int) $_GET['from_post'] ); // phpcs:ignore WordPress.Security.NonceVerification + + // Don't know what WPML does but Polylang does copy all public meta keys by default. + $keys = get_post_custom_keys( $arr['original_post_id'] ); + if ( is_array( $keys ) ) { + foreach ( $keys as $k => $meta_key ) { + if ( is_protected_meta( $meta_key ) ) { + unset( $keys[ $k ] ); + } + } + } + + // Apply our filter and fill the expected output ( see /types/embedded/includes/fields-post.php ) + /** This filter is documented in modules/sync/admin-sync.php */ + $arr['fields'] = array_unique( apply_filters( 'pll_copy_post_metas', empty( $keys ) ? array() : $keys, false ) ); + return $arr; + } +} + +if ( ! function_exists( 'icl_get_default_language' ) ) { + /** + * Undocumented function used by Warp 6 by Yootheme + * + * @since 1.0.5 + * + * @return string|false default language code + */ + function icl_get_default_language() { + return pll_default_language(); + } +} + +if ( ! function_exists( 'wpml_get_default_language' ) ) { + /** + * Undocumented function reported to be used by Table Rate Shipping for WooCommerce + * + * @see https://wordpress.org/support/topic/add-wpml-compatibility-function + * + * @since 1.8.2 + * + * @return string|false default language code + */ + function wpml_get_default_language() { + return pll_default_language(); + } +} + +if ( ! function_exists( 'icl_get_current_language' ) ) { + /** + * Undocumented function used by Ultimate Member + * + * @since 2.2.4 + * + * @return string Current language code + */ + function icl_get_current_language() { + return (string) pll_current_language(); + } +} diff --git a/wp-content/plugins/polylang/polylang.php b/wp-content/plugins/polylang/polylang.php new file mode 100644 index 0000000..ded4cfa --- /dev/null +++ b/wp-content/plugins/polylang/polylang.php @@ -0,0 +1,77 @@ +. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; // Don't access directly. +} + +if ( defined( 'POLYLANG_VERSION' ) ) { + // The user is attempting to activate a second plugin instance, typically Polylang and Polylang Pro. + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + require_once ABSPATH . 'wp-includes/pluggable.php'; + if ( is_plugin_active( plugin_basename( __FILE__ ) ) ) { + deactivate_plugins( plugin_basename( __FILE__ ) ); // Deactivate this plugin. + // WP does not allow us to send a custom meaningful message, so just tell the plugin has been deactivated. + wp_safe_redirect( add_query_arg( 'deactivate', 'true', remove_query_arg( 'activate' ) ) ); + exit; + } +} else { + // Go on loading the plugin + define( 'POLYLANG_VERSION', '3.7.7' ); + define( 'PLL_MIN_WP_VERSION', '6.2' ); + define( 'PLL_MIN_PHP_VERSION', '7.2' ); + + define( 'POLYLANG_FILE', __FILE__ ); + define( 'POLYLANG_DIR', __DIR__ ); + + // Whether we are using Polylang or Polylang Pro, get the filename of the plugin in use. + if ( ! defined( 'POLYLANG_ROOT_FILE' ) ) { + define( 'POLYLANG_ROOT_FILE', __FILE__ ); + } + + if ( ! defined( 'POLYLANG_BASENAME' ) ) { + define( 'POLYLANG_BASENAME', plugin_basename( __FILE__ ) ); // Plugin name as known by WP. + require __DIR__ . '/vendor/autoload.php'; + } + + define( 'POLYLANG', ucwords( str_replace( '-', ' ', dirname( POLYLANG_BASENAME ) ) ) ); + + if ( empty( $_GET['deactivate-polylang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + new Polylang(); + } +} diff --git a/wp-content/plugins/polylang/readme.txt b/wp-content/plugins/polylang/readme.txt new file mode 100644 index 0000000..2b6b645 --- /dev/null +++ b/wp-content/plugins/polylang/readme.txt @@ -0,0 +1,216 @@ +=== Polylang === +Contributors: Chouby, manooweb, raaaahman, marianne38, sebastienserre, greglone, hugod +Donate link: https://polylang.pro +Tags: multilingual, translate, translation, language, localization +Requires at least: 6.2 +Tested up to: 6.9 +Requires PHP: 7.2 +Stable tag: 3.7.7 +License: GPLv3 or later +License URI: https://www.gnu.org/licenses/gpl-3.0.html + +Go multilingual in a simple and efficient way. Keep writing posts and taxonomy terms as usual while defining their languages all at once. + +== Description == + +With Polylang fully integrated to WordPress and using only its built-in core features (taxonomies), keep steady performances on your site and create a multilingual site featuring from just one extra language to 10 or more depending on your needs. There is no limit in the number of languages added and WordPress’ language packs are automatically downloaded when ready. + += Features = + +Depending on the type of site you have built or are planning to build, a combination of plugins from the list below might be of interest. +All plugins include a wizard allowing to setup them in just a few clicks. + +### Polylang + +Polylang and [Polylang Pro](https://polylang.pro) share the same core providing features such as: + +* Translating posts, pages, media, categories, post tags, custom post types and taxonomies, RSS feeds; RTL scripts are supported. +* The language is either set by the language code in URL, or you can use a different sub-domain or domain per language. +* Automatic copy of categories, post tags and other metas when creating a new post or page translation. +* Translating classic menus and classic widgets. Also accessible with [Site Editor Classic Features](https://wordpress.org/plugins/fse-classic/) in block themes. +* Customizable language switcher available as a classic widget or a classic navigation menu item. +* Compatibility with Yoast SEO. + +### Polylang Pro + +Helps optimizing the time spent translating your site with some very useful extra features such as: + +* Better integration in the new Block Editor. +* Language switcher available as a block. +* Language options available in the widget block editor. +* Template parts translatable in the site editor (FSE). +* Duplicate and/or synchronize content across post translations. +* Improved compatibility with other plugins such as [ACF Pro](https://polylang.pro/doc/working-with-acf-pro/). +* Share the same URL slug for posts or terms across languages. +* [Translate URL slugs](https://polylang.pro/doc/translating-urls-slugs/) for categories, author bases, custom post types and more... +* Machine translation with DeepL. +* Export and import of content in XLIFF format for outsourced professional translation. +* **Access to a Premium Support for personalized assistance.** + +### Polylang for WooCommerce + +[Add-on](https://polylang.pro/downloads/polylang-for-woocommerce/) for the compatibility with WooCommerce which provides features such as: + +* Translating WooCommerce pages (shop, check-out, cart, my account), product categories and global attribute terms directly in the WooCommerce interface. +* Translating WooCommerce e-mails and sending them to customers in their language. +* Products metadata synchronization. +* Compatibility with the native WooCommerce CSV import & export tool. +* Compatibility with popular plugins such as WooCommerce Subscriptions, Product Bundles, WooCommerce Bookings, Shipment Tracking and more. +* Ability to use the WooCommerce REST API (available with Polylang Pro). +* **Access to a Premium Support for personalized assistance.** + += Our other free plugins = + +* [WPML to Polylang](https://wordpress.org/plugins/wpml-to-polylang/) allows migrating from WPML to Polylang. +* [Site Editor Classic Features](https://wordpress.org/plugins/fse-classic/) allows to use classic widgets (including the Polylang language switcher) and menus in the site editor (FSE). + += Credits = + +Thanks a lot to all translators who [help translating Polylang](https://translate.wordpress.org/projects/wp-plugins/polylang). +Thanks a lot to [Alex Lopez](http://www.alexlopez.rocks/) for the design of the logo. +Most of the flags included with Polylang are coming from [famfamfam](http://famfamfam.com/) and are public domain. +Wherever third party code has been used, credit has been given in the code’s comments. + +== Installation == + +1. Make sure you are using WordPress 6.2 or later and that your server is running PHP 7.2 or later (same requirement as WordPress itself). +1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results! +1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress. +1. The [setup wizard](https://polylang.pro/documentation/support/getting-started/setup-wizard/) is automatically launched to help you get started more easily with Polylang by configuring the main features. + +== Frequently Asked Questions == + += Where to find help ? = + +* First time users should read [Polylang - Getting started](https://polylang.pro/documentation/support/getting-started/), which explains the basics and includes a lot of screenshots. +* Read the [documentation](https://polylang.pro/documentation/support/). It includes a [FAQ](https://polylang.pro/documentation/support/faq/) and the [documentation for developers](https://polylang.pro/documentation/support/developers/). +* Search the [community support forum](https://wordpress.org/search/). You will probably find your answers here. +* Read the sticky posts in the [community support forum](http://wordpress.org/support/plugin/polylang). +* If you still have a problem, open a new thread in the [community support forum](http://wordpress.org/support/plugin/polylang). +* [Polylang Pro and Polylang for WooCommerce](https://polylang.pro) users have access to our premium support through helpdesk. + += Is Polylang compatible with WooCommerce? = + +* You need [Polylang for WooCommerce](https://polylang.pro/pricing/polylang-for-woocommerce/), premium addon described above, which will make both plugins work together. + += Where do I report security bugs found in this plugin? = + +* Please report security bugs found in the source code of the Polylang plugin through the [Patchstack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/d83984d2-d748-43e3-88e2-6dd3bd2c881d). The Patchstack team will assist you with verification, CVE assignment, and notify the developers of this plugin. + +== Screenshots == + +1. The Polylang languages admin panel +2. The Strings translations admin panel +3. Multilingual media library +4. The Edit Post screen with the Languages metabox + +== Changelog == + += 3.7.7 (2026-01-26) = + +* Pro: Add DeepL support for 70 new languages. #1793 #2871 +* Fix advanced menu properties not displayed in customizer. Props Viktor Szépe. #1722 +* Fix incorrect slug when changing the language of a term. #1790 + += 3.7.6 (2026-01-06) = + +* Pro: Add support for WP 6.9 new blocks #2790 +* Pro: Fix insertion of navigation language switcher block in WP 6.9 #2847 +* Pro: Fix current post URL in site editor for WP 6.8+ #2843 +* Pro: Fix a conflict with Sensei LMS #2845 +* Fix categories not filtered per post language in quick edit #1785 + += 3.7.5 (2025-11-10) = + +* Pro: Updated DeepL supported languages list +* Pro: Fix a regression in cross domain login +* Pro: Fix post object field not correctly translated +* Pro: Fix a fatal error in EDD plugin updater when the request to the server fails +* Pro: Fix locale fallback in WP 6.8+ +* Pro: Add `pll_enable_acf_labels_translation` filter allowing to disable the translation of ACF labels +* Pro: Fix ACF image field not correctly translated when media translation is active +* Pro: Fix ACF image field in repeater not copied +* Pro: Fix ACF ajax request unexpectedly filtered by language when editing a field group +* Pro: Fix ACF blocks not translated when included inside a non-ACF block +* Pro: Add compatibility of the duplicate tool with Divi +* Fix Undefined array key "terms" in canonical.php #1691 +* Fix some regressions in the WPML API used by YITH WooCommerce Wishlist #1684 +* Fix LinkedIn site title preview with Yoast SEO #1686 +* Fix HTML language attribute on login page #1601 +* Fix conflict with WP Job Manager Application #1749 + += 3.7.4 (2025-10-28) = + +* Pro: Security: Fix a ReDoS vulnerability reported by Janine Moreira. +* Security: Fix deserialization of untrusted data reported by Phat RiO – BlueRock via Patchstack. + += 3.7.3 (2025-06-16) = + +* Pro: Always display ACF translation settings for field groups formerly translated in versions older than 3.7 +* Pro: Fix translation displayed in child fields of ACF layout fields when they should not +* Pro: Fix translation of the ACF field "Choice" when the translation is set to "Copy once" or "Synchronize" +* Pro: Fix a PHP warning when translating some blocks +* Pro: Fix impossibility to change the language of an empty post in the block editor +* Fix duplicate values in options #1672 +* Fix JS error in media library (grid view) #1674 +* Fix language dropdown not present in the media modal accessible from the media library grid view #1675 +* Fix DB error introduce by WooCommerce 9.9 #1681 + += 3.7.2 (2025-05-27) = + +* Pro: Require ACF 6.0+ to activate the integration to avoid fatal errors with older versions +* Pro: Add a new ACF field group setting to decide if translations instructions must be displayed +* Pro: Fix a fatal error when using ACF blocks with ACF < 6.3.0 +* Pro: Fix regression preventing to translate Oembed, URL and Email ACF fields +* Pro: Fix ACF fields not translated when they have a default value +* Pro: Fix empty ACF fields values not copied when the field has a default value +* Pro: Fix ACF field default values not translated when copying a post +* Pro: Fix possible fatal error if invalid types of data are sent for machine translation +* Pro: Fix term meta removed when a term is machine translated +* Fix nav menu locations not saved if invalid data are stored in database #1659 +* Fix parent relationship removed when a new translated term is created without parent #1671 + += 3.7.1 (2025-05-05) = + +* Pro: Do not display ACF fields translations settings when language location is set. +* Pro: Fix ACF taxonomy field terms not synchronized when "Save Terms" and "load terms" settings are activated. +* Pro: Fix duplicate options when using numeric keys for ACF choice fields. + += 3.7 (2025-04-22) = + +* Requires PHP 7.2 as minimum version +* Pro: Add DeepL machine translation for strings +* Pro: Consider Polylang Pro as equivalent to Polylang for plugin dependencies +* Pro: Add the details block and several labels in other blocks to XLIFF files and machine translation +* Pro: Allow to translate metas stored as objects +* Pro: Enhanced multilingual support of archive template hierarchy +* Pro: Wrap the language switcher block in a nav tag +* Pro: Support automatic IDs translation in blocks with new filters `pll_sync_block_rules_for_attributes` and `pll_sync_blocks_xpath_rules` +* Pro: Complete rewrite of ACF integration +* Pro: Add support for ACF blocks, post types and taxonomies. +* Pro: Remove the possibility to translate ACF field groups +* Pro: Add languages in ACF locations +* Pro: Add translation of ACF labels in the strings translations page +* Pro: Fix incorrect count of translated strings when importing strings translations +* Pro: Fix incorrect translation when an XLIFF import updates a term sharing its slug +* Pro: Fix term hierarchy with machine translation +* Pro: Fix indented items of a list block not translated with machine translation +* Pro: Fix navigation block inserted in the wrong language +* Update plugin updater to 1.9.4 +* Add translation of widgets custom html in strings translations #1423 +* Refactor core to manage the plugin options in an object #1451 +* Refactor core to give access to languages management in all contexts #1503 +* Remove the language set from the content option for new installs #1517 +* Allow numbers in language codes #1546 +* Display empty fields in the translations table for untranslated strings (instead of duplicating the original) #1574 +* Add REST API endpoints to manage options and languages #1505 #1569 +* Improve performance by registering the language taxonomy only once #1359 +* Add new API functions to insert and update posts and terms in a given language #1500 #1520 +* Add compatibility with jQuery 4 (planned in core for WP 6.8) #1612 +* Fix translations not loaded when the language is set from the content #1395 +* Fix possible term duplication #1490 +* Fix sanitization of translated options that may impact other strings #1571 +* Fix home link block not translated #1647 +* Fix a conflict with WooCommerce Price Based on Country #1638 + +See [changelog.txt](https://plugins.svn.wordpress.org/polylang/trunk/changelog.txt) for older changelog diff --git a/wp-content/plugins/polylang/settings/flags.php b/wp-content/plugins/polylang/settings/flags.php new file mode 100644 index 0000000..239ac50 --- /dev/null +++ b/wp-content/plugins/polylang/settings/flags.php @@ -0,0 +1,276 @@ + __( 'Andorra', 'polylang' ), + 'ae' => __( 'United Arab Emirates', 'polylang' ), + 'af' => __( 'Afghanistan', 'polylang' ), + 'ag' => __( 'Antigua and Barbuda', 'polylang' ), + 'ai' => __( 'Anguilla', 'polylang' ), + 'al' => __( 'Albania', 'polylang' ), + 'am' => __( 'Armenia', 'polylang' ), + 'an' => __( 'Netherlands Antilles', 'polylang' ), + 'ao' => __( 'Angola', 'polylang' ), + 'ar' => __( 'Argentina', 'polylang' ), + 'arab' => __( 'Arab league', 'polylang' ), + 'as' => __( 'American Samoa', 'polylang' ), + 'at' => __( 'Austria', 'polylang' ), + 'au' => __( 'Australia', 'polylang' ), + 'aw' => __( 'Aruba', 'polylang' ), + 'ax' => __( 'Åland Islands', 'polylang' ), + 'az' => __( 'Azerbaijan', 'polylang' ), + 'ba' => __( 'Bosnia and Herzegovina', 'polylang' ), + 'basque' => __( 'Basque Country', 'polylang' ), + 'bb' => __( 'Barbados', 'polylang' ), + 'bd' => __( 'Bangladesh', 'polylang' ), + 'be' => __( 'Belgium', 'polylang' ), + 'bf' => __( 'Burkina Faso', 'polylang' ), + 'bg' => __( 'Bulgaria', 'polylang' ), + 'bh' => __( 'Bahrain', 'polylang' ), + 'bi' => __( 'Burundi', 'polylang' ), + 'bj' => __( 'Benin', 'polylang' ), + 'bm' => __( 'Bermuda', 'polylang' ), + 'bn' => __( 'Brunei', 'polylang' ), + 'bo' => __( 'Bolivia', 'polylang' ), + 'br' => __( 'Brazil', 'polylang' ), + 'bs' => __( 'Bahamas', 'polylang' ), + 'bt' => __( 'Bhutan', 'polylang' ), + 'bw' => __( 'Botswana', 'polylang' ), + 'by' => __( 'Belarus', 'polylang' ), + 'bz' => __( 'Belize', 'polylang' ), + 'ca' => __( 'Canada', 'polylang' ), + 'catalonia' => __( 'Catalonia', 'polylang' ), + 'cc' => __( 'Cocos', 'polylang' ), + 'cd' => __( 'Democratic Republic of the Congo', 'polylang' ), + 'cf' => __( 'Central African Republic', 'polylang' ), + 'cg' => __( 'Congo', 'polylang' ), + 'ch' => __( 'Switzerland', 'polylang' ), + 'ci' => __( 'Ivory Coast', 'polylang' ), + 'ck' => __( 'Cook Islands', 'polylang' ), + 'cl' => __( 'Chile', 'polylang' ), + 'cm' => __( 'Cameroon', 'polylang' ), + 'cn' => __( 'China', 'polylang' ), + 'co' => __( 'Colombia', 'polylang' ), + 'cr' => __( 'Costa Rica', 'polylang' ), + 'cu' => __( 'Cuba', 'polylang' ), + 'cv' => __( 'Cape Verde', 'polylang' ), + 'cx' => __( 'Christmas Island', 'polylang' ), + 'cy' => __( 'Cyprus', 'polylang' ), + 'cz' => __( 'Czech Republic', 'polylang' ), + 'de' => __( 'Germany', 'polylang' ), + 'dj' => __( 'Djibouti', 'polylang' ), + 'dk' => __( 'Denmark', 'polylang' ), + 'dm' => __( 'Dominica', 'polylang' ), + 'do' => __( 'Dominican Republic', 'polylang' ), + 'dz' => __( 'Algeria', 'polylang' ), + 'ec' => __( 'Ecuador', 'polylang' ), + 'ee' => __( 'Estonia', 'polylang' ), + 'eg' => __( 'Egypt', 'polylang' ), + 'eh' => __( 'Western Sahara', 'polylang' ), + 'england' => __( 'England', 'polylang' ), + 'er' => __( 'Eritrea', 'polylang' ), + 'es' => __( 'Spain', 'polylang' ), + 'esperanto' => __( 'Esperanto', 'polylang' ), + 'et' => __( 'Ethiopia', 'polylang' ), + 'fi' => __( 'Finland', 'polylang' ), + 'fj' => __( 'Fiji', 'polylang' ), + 'fk' => __( 'Falkland Islands', 'polylang' ), + 'fm' => __( 'Micronesia', 'polylang' ), + 'fo' => __( 'Faroe Islands', 'polylang' ), + 'fr' => __( 'France', 'polylang' ), + 'ga' => __( 'Gabon', 'polylang' ), + 'galicia' => __( 'Galicia', 'polylang' ), + 'gb' => __( 'United Kingdom', 'polylang' ), + 'gd' => __( 'Grenada', 'polylang' ), + 'ge' => __( 'Georgia', 'polylang' ), + 'gh' => __( 'Ghana', 'polylang' ), + 'gi' => __( 'Gibraltar', 'polylang' ), + 'gl' => __( 'Greenland', 'polylang' ), + 'gm' => __( 'Gambia', 'polylang' ), + 'gn' => __( 'Guinea', 'polylang' ), + 'gp' => __( 'Guadeloupe', 'polylang' ), + 'gq' => __( 'Equatorial Guinea', 'polylang' ), + 'gr' => __( 'Greece', 'polylang' ), + 'gs' => __( 'South Georgia and the South Sandwich Islands', 'polylang' ), + 'gt' => __( 'Guatemala', 'polylang' ), + 'gu' => __( 'Guam', 'polylang' ), + 'gw' => __( 'Guinea-Bissau', 'polylang' ), + 'gy' => __( 'Guyana', 'polylang' ), + 'hk' => __( 'Hong Kong', 'polylang' ), + 'hm' => __( 'Heard Island and McDonald Islands', 'polylang' ), + 'hn' => __( 'Honduras', 'polylang' ), + 'hr' => __( 'Croatia', 'polylang' ), + 'ht' => __( 'Haiti', 'polylang' ), + 'hu' => __( 'Hungary', 'polylang' ), + 'id' => __( 'Indonesia', 'polylang' ), + 'ie' => __( 'Republic of Ireland', 'polylang' ), + 'il' => __( 'Israel', 'polylang' ), + 'in' => __( 'India', 'polylang' ), + 'io' => __( 'British Indian Ocean Territory', 'polylang' ), + 'iq' => __( 'Iraq', 'polylang' ), + 'ir' => __( 'Iran', 'polylang' ), + 'is' => __( 'Iceland', 'polylang' ), + 'it' => __( 'Italy', 'polylang' ), + 'jm' => __( 'Jamaica', 'polylang' ), + 'jo' => __( 'Jordan', 'polylang' ), + 'jp' => __( 'Japan', 'polylang' ), + 'ke' => __( 'Kenya', 'polylang' ), + 'kg' => __( 'Kyrgyzstan', 'polylang' ), + 'kh' => __( 'Cambodia', 'polylang' ), + 'ki' => __( 'Kiribati', 'polylang' ), + 'km' => __( 'Comoros', 'polylang' ), + 'kn' => __( 'Saint Kitts and Nevis', 'polylang' ), + 'kp' => __( 'North Korea', 'polylang' ), + 'kr' => __( 'South Korea', 'polylang' ), + 'kurdistan' => __( 'Kurdistan', 'polylang' ), + 'kw' => __( 'Kuwait', 'polylang' ), + 'ky' => __( 'Cayman Islands', 'polylang' ), + 'kz' => __( 'Kazakhstan', 'polylang' ), + 'la' => __( 'Laos', 'polylang' ), + 'lb' => __( 'Lebanon', 'polylang' ), + 'lc' => __( 'Saint Lucia', 'polylang' ), + 'li' => __( 'Liechtenstein', 'polylang' ), + 'lk' => __( 'Sri Lanka', 'polylang' ), + 'lr' => __( 'Liberia', 'polylang' ), + 'ls' => __( 'Lesotho', 'polylang' ), + 'lt' => __( 'Lithuania', 'polylang' ), + 'lu' => __( 'Luxembourg', 'polylang' ), + 'lv' => __( 'Latvia', 'polylang' ), + 'ly' => __( 'Libya', 'polylang' ), + 'ma' => __( 'Morocco', 'polylang' ), + 'mc' => __( 'Monaco', 'polylang' ), + 'md' => __( 'Moldova', 'polylang' ), + 'me' => __( 'Montenegro', 'polylang' ), + 'mg' => __( 'Madagascar', 'polylang' ), + 'mh' => __( 'Marshall Islands', 'polylang' ), + 'mk' => __( 'North Macedonia', 'polylang' ), + 'ml' => __( 'Mali', 'polylang' ), + 'mm' => __( 'Myanmar', 'polylang' ), + 'mn' => __( 'Mongolia', 'polylang' ), + 'mo' => __( 'Macao', 'polylang' ), + 'mp' => __( 'Northern Mariana Islands', 'polylang' ), + 'mq' => __( 'Martinique', 'polylang' ), + 'mr' => __( 'Mauritania', 'polylang' ), + 'ms' => __( 'Montserrat', 'polylang' ), + 'mt' => __( 'Malta', 'polylang' ), + 'mu' => __( 'Mauritius', 'polylang' ), + 'mv' => __( 'Maldives', 'polylang' ), + 'mw' => __( 'Malawi', 'polylang' ), + 'mx' => __( 'Mexico', 'polylang' ), + 'my' => __( 'Malaysia', 'polylang' ), + 'mz' => __( 'Mozambique', 'polylang' ), + 'na' => __( 'Namibia', 'polylang' ), + 'nc' => __( 'New Caledonia', 'polylang' ), + 'ne' => __( 'Niger', 'polylang' ), + 'nf' => __( 'Norfolk Island', 'polylang' ), + 'ng' => __( 'Nigeria', 'polylang' ), + 'ni' => __( 'Nicaragua', 'polylang' ), + 'nl' => __( 'Netherlands', 'polylang' ), + 'no' => __( 'Norway', 'polylang' ), + 'np' => __( 'Nepal', 'polylang' ), + 'nr' => __( 'Nauru', 'polylang' ), + 'nu' => __( 'Niue', 'polylang' ), + 'nz' => __( 'New Zealand', 'polylang' ), + 'occitania' => __( 'Occitania', 'polylang' ), + 'om' => __( 'Oman', 'polylang' ), + 'pa' => __( 'Panama', 'polylang' ), + 'pe' => __( 'Peru', 'polylang' ), + 'pf' => __( 'French Polynesia', 'polylang' ), + 'pg' => __( 'Papua New Guinea', 'polylang' ), + 'ph' => __( 'Philippines', 'polylang' ), + 'pk' => __( 'Pakistan', 'polylang' ), + 'pl' => __( 'Poland', 'polylang' ), + 'pm' => __( 'Saint Pierre and Miquelon', 'polylang' ), + 'pn' => __( 'Pitcairn', 'polylang' ), + 'pr' => __( 'Puerto Rico', 'polylang' ), + 'ps' => __( 'Palestinian Territory', 'polylang' ), + 'pt' => __( 'Portugal', 'polylang' ), + 'pw' => __( 'Belau', 'polylang' ), + 'py' => __( 'Paraguay', 'polylang' ), + 'qa' => __( 'Qatar', 'polylang' ), + 'quebec' => __( 'Quebec', 'polylang' ), + 'ro' => __( 'Romania', 'polylang' ), + 'rs' => __( 'Serbia', 'polylang' ), + 'ru' => __( 'Russia', 'polylang' ), + 'rw' => __( 'Rwanda', 'polylang' ), + 'sa' => __( 'Saudi Arabia', 'polylang' ), + 'sb' => __( 'Solomon Islands', 'polylang' ), + 'sc' => __( 'Seychelles', 'polylang' ), + 'scotland' => __( 'Scotland', 'polylang' ), + 'sd' => __( 'Sudan', 'polylang' ), + 'se' => __( 'Sweden', 'polylang' ), + 'sg' => __( 'Singapore', 'polylang' ), + 'sh' => __( 'Saint Helena', 'polylang' ), + 'si' => __( 'Slovenia', 'polylang' ), + 'sk' => __( 'Slovakia', 'polylang' ), + 'sl' => __( 'Sierra Leone', 'polylang' ), + 'sm' => __( 'San Marino', 'polylang' ), + 'sn' => __( 'Senegal', 'polylang' ), + 'so' => __( 'Somalia', 'polylang' ), + 'sr' => __( 'Suriname', 'polylang' ), + 'ss' => __( 'South Sudan', 'polylang' ), + 'st' => __( 'São Tomé and Príncipe', 'polylang' ), + 'sv' => __( 'El Salvador', 'polylang' ), + 'sy' => __( 'Syria', 'polylang' ), + 'sz' => __( 'Swaziland', 'polylang' ), + 'tc' => __( 'Turks and Caicos Islands', 'polylang' ), + 'td' => __( 'Chad', 'polylang' ), + 'tf' => __( 'French Southern Territories', 'polylang' ), + 'tg' => __( 'Togo', 'polylang' ), + 'th' => __( 'Thailand', 'polylang' ), + 'tibet' => __( 'Tibet', 'polylang' ), + 'tj' => __( 'Tajikistan', 'polylang' ), + 'tk' => __( 'Tokelau', 'polylang' ), + 'tl' => __( 'Timor-Leste', 'polylang' ), + 'tm' => __( 'Turkmenistan', 'polylang' ), + 'tn' => __( 'Tunisia', 'polylang' ), + 'to' => __( 'Tonga', 'polylang' ), + 'tr' => __( 'Turkey', 'polylang' ), + 'tt' => __( 'Trinidad and Tobago', 'polylang' ), + 'tv' => __( 'Tuvalu', 'polylang' ), + 'tw' => __( 'Taiwan', 'polylang' ), + 'tz' => __( 'Tanzania', 'polylang' ), + 'ua' => __( 'Ukraine', 'polylang' ), + 'ug' => __( 'Uganda', 'polylang' ), + 'us' => __( 'United States', 'polylang' ), + 'uy' => __( 'Uruguay', 'polylang' ), + 'uz' => __( 'Uzbekistan', 'polylang' ), + 'va' => __( 'Vatican', 'polylang' ), + 'vc' => __( 'Saint Vincent and the Grenadines', 'polylang' ), + 've' => __( 'Venezuela', 'polylang' ), + 'veneto' => __( 'Veneto', 'polylang' ), + 'vg' => __( 'British Virgin Islands', 'polylang' ), + 'vi' => __( 'United States Virgin Islands', 'polylang' ), + 'vn' => __( 'Vietnam', 'polylang' ), + 'vu' => __( 'Vanuatu', 'polylang' ), + 'wales' => __( 'Wales', 'polylang' ), + 'wf' => __( 'Wallis and Futuna', 'polylang' ), + 'ws' => __( 'Western Samoa', 'polylang' ), + 'ye' => __( 'Yemen', 'polylang' ), + 'yt' => __( 'Mayotte', 'polylang' ), + 'za' => __( 'South Africa', 'polylang' ), + 'zm' => __( 'Zambia', 'polylang' ), + 'zw' => __( 'Zimbabwe', 'polylang' ), +); + +/** + * Filter the list of predefined flags + * + * @since 1.8 + * + * @param array $flags + */ +return apply_filters( 'pll_predefined_flags', $flags ); diff --git a/wp-content/plugins/polylang/settings/languages.php b/wp-content/plugins/polylang/settings/languages.php new file mode 100644 index 0000000..2a0a4e2 --- /dev/null +++ b/wp-content/plugins/polylang/settings/languages.php @@ -0,0 +1,1550 @@ + ISO 639-1 language code + * [locale] => WordPress locale + * [name] => name + * [dir] => text direction + * [flag] => flag code + * [w3c] => W3C locale + * [facebook] => Facebook locale + * [deepl] => DeepL locale + * + * Facebook locales without equivalent WordPress locale: + * 'ay_BO' (Aymara) + * 'ck_US' (Cherokee) + * 'en_IN' (English India) + * 'gx_GR' (Classical Greek) + * 'ik_US' (Inupiak) + * 'iu_CA' (Inuktitut) + * 'ja_KS' (Japanese Kansai) + * 'ks_IN' (Cachemiri) + * 'nd_ZW' (Ndebele) + * 'nr_ZA' (Southern Ndebele) + * 'ns_ZA' (Northern Sotho) + * 'ny_MW' (Chewa) + * 'qc_GT' (Quiché) + * 'qu_PE' (Quechua) + * 'se_NO' (Northern Sami) + * 'ss_SZ' (Swazi) + * 'st_ZA' (Southern Sotho) + * 'tl_ST' (Klingon) + * 'tn_BW' (Tswana) + * 'ts_ZA' (Tsonga) + * 've_ZA' (Venda) + * 'yi_DE' (Yiddish) + * 'zz_TR' (Zazaki) + * + * DeepL locales without equivalent WordPress locale: + * 'ACE' (Acehnese) + * 'AY' (Aymara) + * 'GN' (Guarani) + * 'GOM' (Konkani) + * 'PAG' (Pangasinan) + * 'PAM' (Kapampangan) + * 'PRS' (Dari) + * 'QU' (Quechua) + * 'ST' (Sesotho) + * 'TN' (Tswana) + * 'TS' (Tsonga) + * 'YI' (Yiddish) + * 'YUE' (Cantonese) + */ +return array( + 'af' => array( + 'code' => 'af', + 'locale' => 'af', + 'name' => 'Afrikaans', + 'dir' => 'ltr', + 'flag' => 'za', + 'facebook' => 'af_ZA', + 'deepl' => 'AF', + ), + 'ak' => array( + 'facebook' => 'ak_GH', + ), + 'am' => array( + 'code' => 'am', + 'locale' => 'am', + 'name' => 'አማርኛ', + 'dir' => 'ltr', + 'flag' => 'et', + 'facebook' => 'am_ET', + ), + 'ar' => array( + 'code' => 'ar', + 'locale' => 'ar', + 'name' => 'العربية', + 'dir' => 'rtl', + 'flag' => 'arab', + 'facebook' => 'ar_AR', + 'deepl' => 'AR', + ), + 'arg' => array( + 'code' => 'an', + 'locale' => 'arg', + 'name' => 'Aragonés', + 'dir' => 'ltr', + 'flag' => 'es', + 'deepl' => 'AN', + ), + 'arq' => array( + 'facebook' => 'ar_AR', + ), + 'ary' => array( + 'code' => 'ar', + 'locale' => 'ary', + 'name' => 'العربية المغربية', + 'dir' => 'rtl', + 'flag' => 'ma', + 'facebook' => 'ar_AR', + 'deepl' => 'AR', + ), + 'as' => array( + 'code' => 'as', + 'locale' => 'as', + 'name' => 'অসমীয়া', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'as_IN', + 'deepl' => 'AS', + ), + 'az' => array( + 'code' => 'az', + 'locale' => 'az', + 'name' => 'Azərbaycan', + 'dir' => 'ltr', + 'flag' => 'az', + 'facebook' => 'az_AZ', + 'deepl' => 'AZ', + ), + 'azb' => array( + 'code' => 'az', + 'locale' => 'azb', + 'name' => 'گؤنئی آذربایجان', + 'dir' => 'rtl', + 'flag' => 'az', + ), + 'ba' => array( + 'code' => 'ba', + 'locale' => 'ba', + 'name' => 'башҡорт теле', + 'dir' => 'ltr', + 'flag' => 'ru', + 'w3c' => 'ba', + 'deepl' => 'BA', + ), + 'bel' => array( + 'code' => 'be', + 'locale' => 'bel', + 'name' => 'Беларуская мова', + 'dir' => 'ltr', + 'flag' => 'by', + 'w3c' => 'be', + 'facebook' => 'be_BY', + 'deepl' => 'BE', + ), + 'bg_BG' => array( + 'code' => 'bg', + 'locale' => 'bg_BG', + 'name' => 'български', + 'dir' => 'ltr', + 'flag' => 'bg', + 'facebook' => 'bg_BG', + 'deepl' => 'BG', + ), + 'bho' => array( + 'code' => 'bho', + 'locale' => 'bho', + 'name' => 'भोजपुरी', + 'dir' => 'ltr', + 'flag' => 'in', + 'w3c' => 'bho', + 'facebook' => 'bp_IN', + 'deepl' => 'BHO', + ), + 'bn_BD' => array( + 'code' => 'bn', + 'locale' => 'bn_BD', + 'name' => 'বাংলা', + 'dir' => 'ltr', + 'flag' => 'bd', + 'facebook' => 'bn_IN', + 'deepl' => 'BN', + ), + 'bo' => array( + 'code' => 'bo', + 'locale' => 'bo', + 'name' => 'བོད་ཡིག', + 'dir' => 'ltr', + 'flag' => 'tibet', + ), + 'bre' => array( + 'code' => 'br', + 'locale' => 'bre', + 'name' => 'Brezhoneg', + 'dir' => 'ltr', + 'flag' => 'fr', + 'w3c' => 'br', + 'facebook' => 'br_FR', + 'deepl' => 'BR', + ), + 'bs_BA' => array( + 'code' => 'bs', + 'locale' => 'bs_BA', + 'name' => 'Bosanski', + 'dir' => 'ltr', + 'flag' => 'ba', + 'facebook' => 'bs_BA', + 'deepl' => 'BS', + ), + 'ca' => array( + 'code' => 'ca', + 'locale' => 'ca', + 'name' => 'Català', + 'dir' => 'ltr', + 'flag' => 'catalonia', + 'facebook' => 'ca_ES', + 'deepl' => 'CA', + ), + 'ceb' => array( + 'code' => 'ceb', + 'locale' => 'ceb', + 'name' => 'Cebuano', + 'dir' => 'ltr', + 'flag' => 'ph', + 'facebook' => 'cx_PH', + 'deepl' => 'CEB', + ), + 'ckb' => array( + 'code' => 'ku', + 'locale' => 'ckb', + 'name' => 'کوردی', + 'dir' => 'rtl', + 'flag' => 'kurdistan', + 'facebook' => 'cb_IQ', + 'deepl' => 'CKB', + ), + 'co' => array( + 'facebook' => 'co_FR', + ), + 'cs_CZ' => array( + 'code' => 'cs', + 'locale' => 'cs_CZ', + 'name' => 'Čeština', + 'dir' => 'ltr', + 'flag' => 'cz', + 'facebook' => 'cs_CZ', + 'deepl' => 'CS', + ), + 'cy' => array( + 'code' => 'cy', + 'locale' => 'cy', + 'name' => 'Cymraeg', + 'dir' => 'ltr', + 'flag' => 'wales', + 'facebook' => 'cy_GB', + 'deepl' => 'CY', + ), + 'da_DK' => array( + 'code' => 'da', + 'locale' => 'da_DK', + 'name' => 'Dansk', + 'dir' => 'ltr', + 'flag' => 'dk', + 'facebook' => 'da_DK', + 'deepl' => 'DA', + ), + 'de_AT' => array( + 'code' => 'de', + 'locale' => 'de_AT', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'at', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_CH' => array( + 'code' => 'de', + 'locale' => 'de_CH', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'ch', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_CH_informal' => array( + 'code' => 'de', + 'locale' => 'de_CH_informal', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'ch', + 'w3c' => 'de-CH', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_DE' => array( + 'code' => 'de', + 'locale' => 'de_DE', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'de', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_DE_formal' => array( + 'code' => 'de', + 'locale' => 'de_DE_formal', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'de', + 'w3c' => 'de-DE', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'dsb' => array( + 'code' => 'dsb', + 'locale' => 'dsb', + 'name' => 'Dolnoserbšćina', + 'dir' => 'ltr', + 'flag' => 'de', + ), + 'dzo' => array( + 'code' => 'dz', + 'locale' => 'dzo', + 'name' => 'རྫོང་ཁ', + 'dir' => 'ltr', + 'flag' => 'bt', + 'w3c' => 'dz', + ), + 'el' => array( + 'code' => 'el', + 'locale' => 'el', + 'name' => 'Ελληνικά', + 'dir' => 'ltr', + 'flag' => 'gr', + 'facebook' => 'el_GR', + 'deepl' => 'EL', + ), + 'en_AU' => array( + 'code' => 'en', + 'locale' => 'en_AU', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'au', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_CA' => array( + 'code' => 'en', + 'locale' => 'en_CA', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'ca', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_GB' => array( + 'code' => 'en', + 'locale' => 'en_GB', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'gb', + 'facebook' => 'en_GB', + 'deepl' => 'EN-GB', + ), + 'en_NZ' => array( + 'code' => 'en', + 'locale' => 'en_NZ', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'nz', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_US' => array( + 'code' => 'en', + 'locale' => 'en_US', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'us', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_ZA' => array( + 'code' => 'en', + 'locale' => 'en_ZA', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'za', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'eo' => array( + 'code' => 'eo', + 'locale' => 'eo', + 'name' => 'Esperanto', + 'dir' => 'ltr', + 'flag' => 'esperanto', + 'facebook' => 'eo_EO', + 'deepl' => 'EO', + ), + 'es_AR' => array( + 'code' => 'es', + 'locale' => 'es_AR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'ar', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_CL' => array( + 'code' => 'es', + 'locale' => 'es_CL', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'cl', + 'facebook' => 'es_CL', + 'deepl' => 'ES-419', + ), + 'es_CO' => array( + 'code' => 'es', + 'locale' => 'es_CO', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'co', + 'facebook' => 'es_CO', + 'deepl' => 'ES-419', + ), + 'es_CR' => array( + 'code' => 'es', + 'locale' => 'es_CR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'cr', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_DO' => array( + 'code' => 'es', + 'locale' => 'es_DO', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'do', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_EC' => array( + 'code' => 'es', + 'locale' => 'es_EC', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'ec', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_ES' => array( + 'code' => 'es', + 'locale' => 'es_ES', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'es', + 'facebook' => 'es_ES', + 'deepl' => 'ES', + ), + 'es_GT' => array( + 'code' => 'es', + 'locale' => 'es_GT', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'gt', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_MX' => array( + 'code' => 'es', + 'locale' => 'es_MX', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'mx', + 'facebook' => 'es_MX', + 'deepl' => 'ES-419', + ), + 'es_PE' => array( + 'code' => 'es', + 'locale' => 'es_PE', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'pe', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_PR' => array( + 'code' => 'es', + 'locale' => 'es_PR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'pr', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_UY' => array( + 'code' => 'es', + 'locale' => 'es_UY', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'uy', + 'facebook' => 'es_LA', + 'deepl' => 'ES-419', + ), + 'es_VE' => array( + 'code' => 'es', + 'locale' => 'es_VE', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 've', + 'facebook' => 'es_VE', + 'deepl' => 'ES-419', + ), + 'et' => array( + 'code' => 'et', + 'locale' => 'et', + 'name' => 'Eesti', + 'dir' => 'ltr', + 'flag' => 'ee', + 'facebook' => 'et_EE', + 'deepl' => 'ET', + ), + 'eu' => array( + 'code' => 'eu', + 'locale' => 'eu', + 'name' => 'Euskara', + 'dir' => 'ltr', + 'flag' => 'basque', + 'facebook' => 'eu_ES', + 'deepl' => 'EU', + ), + 'fa_AF' => array( + 'code' => 'fa', + 'locale' => 'fa_AF', + 'name' => 'فارسی', + 'dir' => 'rtl', + 'flag' => 'af', + 'facebook' => 'fa_IR', + 'deepl' => 'FA', + ), + 'fa_IR' => array( + 'code' => 'fa', + 'locale' => 'fa_IR', + 'name' => 'فارسی', + 'dir' => 'rtl', + 'flag' => 'ir', + 'facebook' => 'fa_IR', + 'deepl' => 'FA', + ), + 'fi' => array( + 'code' => 'fi', + 'locale' => 'fi', + 'name' => 'Suomi', + 'dir' => 'ltr', + 'flag' => 'fi', + 'facebook' => 'fi_FI', + 'deepl' => 'FI', + ), + 'fo' => array( + 'code' => 'fo', + 'locale' => 'fo', + 'name' => 'Føroyskt', + 'dir' => 'ltr', + 'flag' => 'fo', + 'facebook' => 'fo_FO', + ), + 'fr_BE' => array( + 'code' => 'fr', + 'locale' => 'fr_BE', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'be', + 'facebook' => 'fr_FR', + 'deepl' => 'FR', + ), + 'fr_CA' => array( + 'code' => 'fr', + 'locale' => 'fr_CA', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'quebec', + 'facebook' => 'fr_CA', + 'deepl' => 'FR', + ), + 'fr_FR' => array( + 'code' => 'fr', + 'locale' => 'fr_FR', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'fr', + 'facebook' => 'fr_FR', + 'deepl' => 'FR', + ), + 'fuc' => array( + 'facebook' => 'ff_NG', + ), + 'fur' => array( + 'code' => 'fur', + 'locale' => 'fur', + 'name' => 'Furlan', + 'dir' => 'ltr', + 'flag' => 'it', + ), + 'fy' => array( + 'code' => 'fy', + 'locale' => 'fy', + 'name' => 'Frysk', + 'dir' => 'ltr', + 'flag' => 'nl', + 'facebook' => 'fy_NL', + ), + 'ga' => array( + 'code' => 'ga', + 'locale' => 'ga', + 'name' => 'Gaelige', + 'dir' => 'ltr', + 'flag' => 'ie', + 'w3c' => 'ga', + 'facebook' => 'ga_IE', + 'deepl' => 'GA', + ), + 'gax' => array( + 'code' => 'om', + 'locale' => 'gax', + 'name' => 'Afaan Oromoo', + 'dir' => 'ltr', + 'flag' => 'et', + 'w3c' => 'gax', + 'facebook' => 'om_ET', + 'deepl' => 'OM', + ), + 'gd' => array( + 'code' => 'gd', + 'locale' => 'gd', + 'name' => 'Gàidhlig', + 'dir' => 'ltr', + 'flag' => 'scotland', + ), + 'gl_ES' => array( + 'code' => 'gl', + 'locale' => 'gl_ES', + 'name' => 'Galego', + 'dir' => 'ltr', + 'flag' => 'galicia', + 'facebook' => 'gl_ES', + 'deepl' => 'GL', + ), + 'gn' => array( + 'facebook' => 'gn_PY', + ), + 'gu' => array( + 'code' => 'gu', + 'locale' => 'gu', + 'name' => 'ગુજરાતી', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'gu_IN', + 'deepl' => 'GU', + ), + 'hat' => array( + 'w3c' => 'ht', + 'facebook' => 'ht_HT', + 'deepl' => 'HT', + ), + 'hau' => array( + 'code' => 'ha', + 'locale' => 'hau', + 'name' => 'Harshen Hausa', + 'dir' => 'ltr', + 'flag' => 'ng', + 'w3c' => 'ha', + 'facebook' => 'ha_NG', + 'deepl' => 'HA', + ), + 'haz' => array( + 'code' => 'haz', + 'locale' => 'haz', + 'name' => 'هزاره گی', + 'dir' => 'rtl', + 'flag' => 'af', + ), + 'he_IL' => array( + 'code' => 'he', + 'locale' => 'he_IL', + 'name' => 'עברית', + 'dir' => 'rtl', + 'flag' => 'il', + 'facebook' => 'he_IL', + 'deepl' => 'HE', + ), + 'hi_IN' => array( + 'code' => 'hi', + 'locale' => 'hi_IN', + 'name' => 'हिन्दी', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'hi_IN', + 'deepl' => 'HI', + ), + 'hr' => array( + 'code' => 'hr', + 'locale' => 'hr', + 'name' => 'Hrvatski', + 'dir' => 'ltr', + 'flag' => 'hr', + 'facebook' => 'hr_HR', + 'deepl' => 'HR', + ), + 'hsb' => array( + 'code' => 'hsb', + 'locale' => 'hsb', + 'name' => 'Hornjoserbšćina', + 'dir' => 'ltr', + 'flag' => 'de', + ), + 'hu_HU' => array( + 'code' => 'hu', + 'locale' => 'hu_HU', + 'name' => 'Magyar', + 'dir' => 'ltr', + 'flag' => 'hu', + 'facebook' => 'hu_HU', + 'deepl' => 'HU', + ), + 'hy' => array( + 'code' => 'hy', + 'locale' => 'hy', + 'name' => 'Հայերեն', + 'dir' => 'ltr', + 'flag' => 'am', + 'facebook' => 'hy_AM', + 'deepl' => 'HY', + ), + 'ibo' => array( + 'code' => 'ig', + 'locale' => 'ibo', + 'name' => 'Asụsụ Igbo', + 'dir' => 'ltr', + 'flag' => 'ng', + 'w3c' => 'ig', + 'facebook' => 'ig_NG', + 'deepl' => 'IG', + ), + 'id_ID' => array( + 'code' => 'id', + 'locale' => 'id_ID', + 'name' => 'Bahasa Indonesia', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'id_ID', + 'deepl' => 'ID', + ), + 'ido' => array( + 'w3c' => 'io', + ), + 'is_IS' => array( + 'code' => 'is', + 'locale' => 'is_IS', + 'name' => 'Íslenska', + 'dir' => 'ltr', + 'flag' => 'is', + 'facebook' => 'is_IS', + 'deepl' => 'IS', + ), + 'it_IT' => array( + 'code' => 'it', + 'locale' => 'it_IT', + 'name' => 'Italiano', + 'dir' => 'ltr', + 'flag' => 'it', + 'facebook' => 'it_IT', + 'deepl' => 'IT', + ), + 'ja' => array( + 'code' => 'ja', + 'locale' => 'ja', + 'name' => '日本語', + 'dir' => 'ltr', + 'flag' => 'jp', + 'facebook' => 'ja_JP', + 'deepl' => 'JA', + ), + 'jv_ID' => array( + 'code' => 'jv', + 'locale' => 'jv_ID', + 'name' => 'Basa Jawa', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'jv_ID', + 'deepl' => 'JV', + ), + 'ka_GE' => array( + 'code' => 'ka', + 'locale' => 'ka_GE', + 'name' => 'ქართული', + 'dir' => 'ltr', + 'flag' => 'ge', + 'facebook' => 'ka_GE', + 'deepl' => 'KA', + ), + 'kab' => array( + 'code' => 'kab', + 'locale' => 'kab', + 'name' => 'Taqbaylit', + 'dir' => 'ltr', + 'flag' => 'dz', + ), + 'kin' => array( + 'code' => 'rw', + 'locale' => 'kin', + 'name' => 'Ikinyarwanda', + 'dir' => 'ltr', + 'flag' => 'rw', + 'w3c' => 'rw', + 'facebook' => 'rw_RW', + ), + 'kir' => array( + 'code' => 'ky', + 'locale' => 'kir', + 'name' => 'Кыргызча', + 'dir' => 'ltr', + 'flag' => 'kg', + 'deepl' => 'KY', + ), + 'kk' => array( + 'code' => 'kk', + 'locale' => 'kk', + 'name' => 'Қазақ тілі', + 'dir' => 'ltr', + 'flag' => 'kz', + 'facebook' => 'kk_KZ', + 'deepl' => 'KK', + ), + 'km' => array( + 'code' => 'km', + 'locale' => 'km', + 'name' => 'ភាសាខ្មែរ', + 'dir' => 'ltr', + 'flag' => 'kh', + 'facebook' => 'km_KH', + ), + 'kmr' => array( + 'deepl' => 'KMR', + ), + 'kn' => array( + 'code' => 'kn', + 'locale' => 'kn', + 'name' => 'ಕನ್ನಡ', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'kn_IN', + ), + 'ko_KR' => array( + 'code' => 'ko', + 'locale' => 'ko_KR', + 'name' => '한국어', + 'dir' => 'ltr', + 'flag' => 'kr', + 'facebook' => 'ko_KR', + 'deepl' => 'KO', + ), + 'ku' => array( + 'facebook' => 'ku_TR', + ), + 'ky_KY' => array( + 'facebook' => 'ky_KG', + ), + 'la' => array( + 'facebook' => 'la_VA', + 'deepl' => 'LA', + ), + 'lb_LU' => array( + 'code' => 'lb', + 'locale' => 'lb_LU', + 'name' => 'Lëtzebuergesch', + 'dir' => 'ltr', + 'flag' => 'lu', + 'w3c' => 'lb', + 'deepl' => 'LB', + ), + 'li' => array( + 'facebook' => 'li_NL', + ), + 'lin' => array( + 'code' => 'ln', + 'locale' => 'lin', + 'name' => 'Ngala', + 'dir' => 'ltr', + 'flag' => 'cd', + 'w3c' => 'ln', + 'facebook' => 'ln_CD', + 'deepl' => 'LN', + ), + 'lmo' => array( + 'code' => 'lmo', + 'locale' => 'lmo', + 'name' => 'Lombardo', + 'dir' => 'ltr', + 'flag' => 'it', + 'w3c' => 'lmo', + 'deepl' => 'LMO', + ), + 'lo' => array( + 'code' => 'lo', + 'locale' => 'lo', + 'name' => 'ພາສາລາວ', + 'dir' => 'ltr', + 'flag' => 'la', + 'facebook' => 'lo_LA', + ), + 'lt_LT' => array( + 'code' => 'lt', + 'locale' => 'lt_LT', + 'name' => 'Lietuviškai', + 'dir' => 'ltr', + 'flag' => 'lt', + 'facebook' => 'lt_LT', + 'deepl' => 'LT', + ), + 'lug' => array( + 'code' => 'lg', + 'locale' => 'lug', + 'name' => 'Oluganda', + 'dir' => 'ltr', + 'flag' => 'ug', + 'w3c' => 'lg', + 'facebook' => 'lg_UG', + ), + 'lv' => array( + 'code' => 'lv', + 'locale' => 'lv', + 'name' => 'Latviešu valoda', + 'dir' => 'ltr', + 'flag' => 'lv', + 'facebook' => 'lv_LV', + 'deepl' => 'LV', + ), + 'mai' => array( + 'code' => 'mai', + 'locale' => 'mai', + 'name' => 'मैथिली', + 'dir' => 'ltr', + 'flag' => 'in', + 'w3c' => 'mai', + 'deepl' => 'MAI', + ), + 'mg_MG' => array( + 'code' => 'mg', + 'locale' => 'mg_MG', + 'name' => 'malagasy', + 'dir' => 'ltr', + 'flag' => 'mg', + 'facebook' => 'mg_MG', + 'deepl' => 'MG', + ), + 'mk_MK' => array( + 'code' => 'mk', + 'locale' => 'mk_MK', + 'name' => 'македонски јазик', + 'dir' => 'ltr', + 'flag' => 'mk', + 'facebook' => 'mk_MK', + 'deepl' => 'MK', + ), + 'ml_IN' => array( + 'code' => 'ml', + 'locale' => 'ml_IN', + 'name' => 'മലയാളം', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'ml_IN', + 'deepl' => 'ML', + ), + 'mlt' => array( + 'code' => 'mt', + 'locale' => 'mlt', + 'name' => 'Malti', + 'dir' => 'ltr', + 'flag' => 'mt', + 'facebook' => 'mt_MT', + 'deepl' => 'MT', + ), + 'mn' => array( + 'code' => 'mn', + 'locale' => 'mn', + 'name' => 'Монгол хэл', + 'dir' => 'ltr', + 'flag' => 'mn', + 'facebook' => 'mn_MN', + 'deepl' => 'MN', + ), + 'mr' => array( + 'code' => 'mr', + 'locale' => 'mr', + 'name' => 'मराठी', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'mr_IN', + 'deepl' => 'MR', + ), + 'mri' => array( + 'code' => 'mi', + 'locale' => 'mri', + 'name' => 'Te reo Māori', + 'dir' => 'ltr', + 'flag' => 'nz', + 'w3c' => 'mi', + 'facebook' => 'mi_NZ', + 'deepl' => 'MI', + ), + 'ms_MY' => array( + 'code' => 'ms', + 'locale' => 'ms_MY', + 'name' => 'Bahasa Melayu', + 'dir' => 'ltr', + 'flag' => 'my', + 'facebook' => 'ms_MY', + 'deepl' => 'MS', + ), + 'my_MM' => array( + 'code' => 'my', + 'locale' => 'my_MM', + 'name' => 'ဗမာစာ', + 'dir' => 'ltr', + 'flag' => 'mm', + 'facebook' => 'my_MM', + 'deepl' => 'MY', + ), + 'nb_NO' => array( + 'code' => 'nb', + 'locale' => 'nb_NO', + 'name' => 'Norsk Bokmål', + 'dir' => 'ltr', + 'flag' => 'no', + 'facebook' => 'nb_NO', + 'deepl' => 'NB', + ), + 'ne_NP' => array( + 'code' => 'ne', + 'locale' => 'ne_NP', + 'name' => 'नेपाली', + 'dir' => 'ltr', + 'flag' => 'np', + 'facebook' => 'ne_NP', + 'deepl' => 'NE', + ), + 'nl_BE' => array( + 'code' => 'nl', + 'locale' => 'nl_BE', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'be', + 'facebook' => 'nl_BE', + 'deepl' => 'NL', + ), + 'nl_NL' => array( + 'code' => 'nl', + 'locale' => 'nl_NL', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'nl', + 'facebook' => 'nl_NL', + 'deepl' => 'NL', + ), + 'nl_NL_formal' => array( + 'code' => 'nl', + 'locale' => 'nl_NL_formal', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'nl', + 'w3c' => 'nl-NL', + 'facebook' => 'nl_NL', + 'deepl' => 'NL', + ), + 'nn_NO' => array( + 'code' => 'nn', + 'locale' => 'nn_NO', + 'name' => 'Norsk Nynorsk', + 'dir' => 'ltr', + 'flag' => 'no', + 'facebook' => 'nn_NO', + ), + 'oci' => array( + 'code' => 'oc', + 'locale' => 'oci', + 'name' => 'Occitan', + 'dir' => 'ltr', + 'flag' => 'occitania', + 'w3c' => 'oc', + 'deepl' => 'OC', + ), + 'ory' => array( + 'facebook' => 'or_IN', + ), + 'pa_IN' => array( + 'code' => 'pa', + 'locale' => 'pa_IN', + 'name' => 'ਪੰਜਾਬੀ', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'pa_IN', + 'deepl' => 'PA', + ), + 'pl_PL' => array( + 'code' => 'pl', + 'locale' => 'pl_PL', + 'name' => 'Polski', + 'dir' => 'ltr', + 'flag' => 'pl', + 'facebook' => 'pl_PL', + 'deepl' => 'PL', + ), + 'ps' => array( + 'code' => 'ps', + 'locale' => 'ps', + 'name' => 'پښتو', + 'dir' => 'rtl', + 'flag' => 'af', + 'facebook' => 'ps_AF', + 'deepl' => 'PS', + ), + 'pt_AO' => array( + 'code' => 'pt', + 'locale' => 'pt_AO', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'ao', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'pt_BR' => array( + 'code' => 'pt', + 'locale' => 'pt_BR', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'br', + 'facebook' => 'pt_BR', + 'deepl' => 'PT-BR', + ), + 'pt_PT' => array( + 'code' => 'pt', + 'locale' => 'pt_PT', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'pt', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'pt_PT_ao90' => array( + 'code' => 'pt', + 'locale' => 'pt_PT_ao90', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'pt', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'rhg' => array( + 'code' => 'rhg', + 'locale' => 'rhg', + 'name' => 'Ruáinga', + 'dir' => 'ltr', + 'flag' => 'mm', + ), + 'ro_RO' => array( + 'code' => 'ro', + 'locale' => 'ro_RO', + 'name' => 'Română', + 'dir' => 'ltr', + 'flag' => 'ro', + 'facebook' => 'ro_RO', + 'deepl' => 'RO', + ), + 'roh' => array( + 'w3c' => 'rm', + 'facebook' => 'rm_CH', + ), + 'ru_RU' => array( + 'code' => 'ru', + 'locale' => 'ru_RU', + 'name' => 'Русский', + 'dir' => 'ltr', + 'flag' => 'ru', + 'facebook' => 'ru_RU', + 'deepl' => 'RU', + ), + 'sa_IN' => array( + 'code' => 'sa', + 'locale' => 'sa_IN', + 'name' => 'संस्कृतम्', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'sa_IN', + 'deepl' => 'SA', + ), + 'sah' => array( + 'code' => 'sah', + 'locale' => 'sah', + 'name' => 'Сахалыы', + 'dir' => 'ltr', + 'flag' => 'ru', + ), + 'scn' => array( + 'code' => 'scn', + 'locale' => 'scn', + 'name' => 'Sicilianu', + 'dir' => 'ltr', + 'flag' => 'it', + 'w3c' => 'scn', + 'deepl' => 'SCN', + ), + 'si_LK' => array( + 'code' => 'si', + 'locale' => 'si_LK', + 'name' => 'සිංහල', + 'dir' => 'ltr', + 'flag' => 'lk', + 'facebook' => 'si_LK', + ), + 'sk_SK' => array( + 'code' => 'sk', + 'locale' => 'sk_SK', + 'name' => 'Slovenčina', + 'dir' => 'ltr', + 'flag' => 'sk', + 'facebook' => 'sk_SK', + 'deepl' => 'SK', + ), + 'skr' => array( + 'code' => 'skr', + 'locale' => 'skr', + 'name' => 'سرائیکی', + 'dir' => 'rtl', + 'flag' => 'pk', + ), + 'sl_SI' => array( + 'code' => 'sl', + 'locale' => 'sl_SI', + 'name' => 'Slovenščina', + 'dir' => 'ltr', + 'flag' => 'si', + 'facebook' => 'sl_SI', + 'deepl' => 'SL', + ), + 'sna' => array( + 'code' => 'sn', + 'locale' => 'sna', + 'name' => 'ChiShona', + 'dir' => 'ltr', + 'flag' => 'zw', + 'w3c' => 'sn', + 'facebook' => 'sn_ZW', + ), + 'snd' => array( + 'code' => 'sd', + 'locale' => 'snd', + 'name' => 'سنڌي', + 'dir' => 'rtl', + 'flag' => 'pk', + ), + 'so_SO' => array( + 'code' => 'so', + 'locale' => 'so_SO', + 'name' => 'Af-Soomaali', + 'dir' => 'ltr', + 'flag' => 'so', + 'facebook' => 'so_SO', + ), + 'sq' => array( + 'code' => 'sq', + 'locale' => 'sq', + 'name' => 'Shqip', + 'dir' => 'ltr', + 'flag' => 'al', + 'facebook' => 'sq_AL', + 'deepl' => 'SQ', + ), + 'sr_RS' => array( + 'code' => 'sr', + 'locale' => 'sr_RS', + 'name' => 'Српски језик', + 'dir' => 'ltr', + 'flag' => 'rs', + 'facebook' => 'sr_RS', + 'deepl' => 'SR', + ), + 'srd' => array( + 'w3c' => 'sc', + 'facebook' => 'sc_IT', + ), + 'su_ID' => array( + 'code' => 'su', + 'locale' => 'su_ID', + 'name' => 'Basa Sunda', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'su_ID', + 'deepl' => 'SU', + ), + 'sv_SE' => array( + 'code' => 'sv', + 'locale' => 'sv_SE', + 'name' => 'Svenska', + 'dir' => 'ltr', + 'flag' => 'se', + 'facebook' => 'sv_SE', + 'deepl' => 'SV', + ), + 'sw' => array( + 'code' => 'sw', + 'locale' => 'sw', + 'name' => 'Kiswahili', + 'dir' => 'ltr', + 'flag' => 'ke', + 'facebook' => 'sw_KE', + 'deepl' => 'SW', + ), + 'syr' => array( + 'facebook' => 'sy_SY', + ), + 'szl' => array( + 'code' => 'szl', + 'locale' => 'szl', + 'name' => 'Ślōnskŏ gŏdka', + 'dir' => 'ltr', + 'flag' => 'pl', + 'facebook' => 'sz_PL', + ), + 'ta_IN' => array( + 'code' => 'ta', + 'locale' => 'ta_IN', + 'name' => 'தமிழ்', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'ta_IN', + 'deepl' => 'TA', + ), + 'ta_LK' => array( + 'code' => 'ta', + 'locale' => 'ta_LK', + 'name' => 'தமிழ்', + 'dir' => 'ltr', + 'flag' => 'lk', + 'facebook' => 'ta_IN', + 'deepl' => 'TA', + ), + 'tah' => array( + 'code' => 'ty', + 'locale' => 'tah', + 'name' => 'Reo Tahiti', + 'dir' => 'ltr', + 'flag' => 'pf', + ), + 'te' => array( + 'code' => 'te', + 'locale' => 'te', + 'name' => 'తెలుగు', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'te_IN', + 'deepl' => 'TE', + ), + 'tg' => array( + 'code' => 'tg', + 'locale' => 'tg', + 'name' => 'Тоҷикӣ', + 'dir' => 'ltr', + 'flag' => 'tj', + 'facebook' => 'tg_TJ', + 'deepl' => 'TG', + ), + 'th' => array( + 'code' => 'th', + 'locale' => 'th', + 'name' => 'ไทย', + 'dir' => 'ltr', + 'flag' => 'th', + 'facebook' => 'th_TH', + 'deepl' => 'TH', + ), + 'tir' => array( + 'code' => 'ti', + 'locale' => 'tir', + 'name' => 'ትግርኛ', + 'dir' => 'ltr', + 'flag' => 'et', + 'w3c' => 'ti', + ), + 'tl' => array( + 'code' => 'tl', + 'locale' => 'tl', + 'name' => 'Tagalog', + 'dir' => 'ltr', + 'flag' => 'ph', + 'facebook' => 'tl_PH', + 'deepl' => 'TL', + ), + 'tr_TR' => array( + 'code' => 'tr', + 'locale' => 'tr_TR', + 'name' => 'Türkçe', + 'dir' => 'ltr', + 'flag' => 'tr', + 'facebook' => 'tr_TR', + 'deepl' => 'TR', + ), + 'tt_RU' => array( + 'code' => 'tt', + 'locale' => 'tt_RU', + 'name' => 'Татар теле', + 'dir' => 'ltr', + 'flag' => 'ru', + 'facebook' => 'tt_RU', + 'deepl' => 'TT', + ), + 'tuk' => array( + 'w3c' => 'tk', + 'facebook' => 'tk_TM', + 'deepl' => 'TK', + ), + 'tzm' => array( + 'facebook' => 'tz_MA', + ), + 'ug_CN' => array( + 'code' => 'ug', + 'locale' => 'ug_CN', + 'name' => 'Uyƣurqə', + 'dir' => 'ltr', + 'flag' => 'cn', + ), + 'uk' => array( + 'code' => 'uk', + 'locale' => 'uk', + 'name' => 'Українська', + 'dir' => 'ltr', + 'flag' => 'ua', + 'facebook' => 'uk_UA', + 'deepl' => 'UK', + ), + 'ur' => array( + 'code' => 'ur', + 'locale' => 'ur', + 'name' => 'اردو', + 'dir' => 'rtl', + 'flag' => 'pk', + 'facebook' => 'ur_PK', + 'deepl' => 'UR', + ), + 'uz_UZ' => array( + 'code' => 'uz', + 'locale' => 'uz_UZ', + 'name' => 'Oʻzbek', + 'dir' => 'ltr', + 'flag' => 'uz', + 'facebook' => 'uz_UZ', + 'deepl' => 'UZ', + ), + 'vec' => array( + 'code' => 'vec', + 'locale' => 'vec', + 'name' => 'Vèneto', + 'dir' => 'ltr', + 'flag' => 'veneto', + ), + 'vi' => array( + 'code' => 'vi', + 'locale' => 'vi', + 'name' => 'Tiếng Việt', + 'dir' => 'ltr', + 'flag' => 'vn', + 'facebook' => 'vi_VN', + 'deepl' => 'VI', + ), + 'wol' => array( + 'code' => 'wo', + 'locale' => 'wol', + 'name' => 'Wolof', + 'dir' => 'ltr', + 'flag' => 'sn', + 'w3c' => 'wo', + 'facebook' => 'wo_SN', + 'deepl' => 'WO', + ), + 'xho' => array( + 'code' => 'xh', + 'locale' => 'xho', + 'name' => 'isiXhosa', + 'dir' => 'ltr', + 'flag' => 'za', + 'w3c' => 'xh', + 'facebook' => 'xh_ZA', + 'deepl' => 'XH', + ), + 'yor' => array( + 'code' => 'yo', + 'locale' => 'yor', + 'name' => 'Yorùbá', + 'dir' => 'ltr', + 'flag' => 'ng', + 'w3c' => 'yo', + 'facebook' => 'yo_NG', + ), + 'zh_CN' => array( + 'code' => 'zh', + 'locale' => 'zh_CN', + 'name' => '中文 (中国)', + 'dir' => 'ltr', + 'flag' => 'cn', + 'facebook' => 'zh_CN', + 'deepl' => 'ZH-HANS', + ), + 'zh_HK' => array( + 'code' => 'zh', + 'locale' => 'zh_HK', + 'name' => '中文 (香港)', + 'dir' => 'ltr', + 'flag' => 'hk', + 'facebook' => 'zh_HK', + 'deepl' => 'ZH-HANT', + ), + 'zh_TW' => array( + 'code' => 'zh', + 'locale' => 'zh_TW', + 'name' => '中文 (台灣)', + 'dir' => 'ltr', + 'flag' => 'tw', + 'facebook' => 'zh_TW', + 'deepl' => 'ZH-HANT', + ), + 'zul' => array( + 'code' => 'zu', + 'locale' => 'zul', + 'name' => 'isiZulu', + 'dir' => 'ltr', + 'flag' => 'za', + 'w3c' => 'zu', + 'facebook' => 'zu_ZA', + 'deepl' => 'ZU', + ), +); diff --git a/wp-content/plugins/polylang/settings/settings-browser.php b/wp-content/plugins/polylang/settings/settings-browser.php new file mode 100644 index 0000000..80b4e30 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-browser.php @@ -0,0 +1,107 @@ +is_available()`, which is used before calling the parent's constructor. + $this->options = &$polylang->options; + + parent::__construct( + $polylang, + array( + 'module' => 'browser', + 'title' => __( 'Detect browser language', 'polylang' ), + 'description' => __( 'When the front page is visited, redirects to itself in the browser preferred language. As this doesn\'t work if it is cached, Polylang will attempt to disable the front page cache for known cache plugins.', 'polylang' ), + 'active_option' => $this->is_available() ? 'browser' : 'none', + ) + ); + + if ( ! class_exists( 'PLL_Xdata_Domain', true ) ) { + add_action( 'admin_print_footer_scripts', array( $this, 'print_js' ) ); + } + } + + /** + * Tells if the option is available + * + * @since 2.0 + * + * @return bool + */ + protected function is_available() { + return ( 3 > $this->options['force_lang'] ) || class_exists( 'PLL_Xdata_Domain', true ); + } + + /** + * Tells if the module is active + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return $this->is_available() ? parent::is_active() : false; + } + + /** + * Displays the javascript to handle dynamically the change in url modifications + * as the preferred browser language is not used when the language is set from different domains + * + * @since 1.8 + * + * @return void + */ + public function print_js() { + wp_enqueue_script( 'jquery' ); + + if ( parent::is_active() && 3 > $this->options['force_lang'] ) { + $func = 'removeClass( "inactive" ).addClass( "active" )'; + $link = sprintf( '%s', $this->action_links['deactivate'] ); + } + else { + $func = 'removeClass( "active" ).addClass( "inactive" )'; + $link = sprintf( '%s', $this->action_links['activate'] ); + } + + $deactivated = sprintf( '%s', $this->action_links['deactivated'] ); + + ?> + + 'cpt', + 'title' => __( 'Custom post types and Taxonomies', 'polylang' ), + 'description' => __( 'Activate languages and translations management for the custom post types and the taxonomies.', 'polylang' ), + ) + ); + + $public_post_types = get_post_types( array( 'public' => true, '_builtin' => false ) ); + /** This filter is documented in include/model.php */ + $this->post_types = array_unique( apply_filters( 'pll_get_post_types', $public_post_types, true ) ); + + /** This filter is documented in include/model.php */ + $programmatically_active_post_types = array_unique( apply_filters( 'pll_get_post_types', array(), false ) ); + $this->disabled_post_types = array_intersect( $programmatically_active_post_types, $this->post_types ); + + $public_taxonomies = get_taxonomies( array( 'public' => true, '_builtin' => false ) ); + $public_taxonomies = array_diff( $public_taxonomies, get_taxonomies( array( '_pll' => true ) ) ); + /** This filter is documented in include/model.php */ + $this->taxonomies = array_unique( apply_filters( 'pll_get_taxonomies', $public_taxonomies, true ) ); + + /** This filter is documented in include/model.php */ + $programmatically_active_taxonomies = array_unique( apply_filters( 'pll_get_taxonomies', array(), false ) ); + $this->disabled_taxonomies = array_intersect( $programmatically_active_taxonomies, $this->taxonomies ); + } + + /** + * Tells if the module is active + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return ! empty( $this->post_types ) || ! empty( $this->taxonomies ); + } + + /** + * Displays the settings form + * + * @since 1.8 + */ + protected function form() { + if ( ! empty( $this->post_types ) ) {?> +

    +
      + post_types as $post_type ) { + $pt = get_post_type_object( $post_type ); + if ( ! empty( $pt ) ) { + $disabled = in_array( $post_type, $this->disabled_post_types ); + printf( + '
    • ', + esc_attr( $post_type ), + checked( $disabled || in_array( $post_type, $this->options['post_types'], true ), true, false ), + disabled( $disabled, true, false ), + esc_html( + sprintf( + /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */ + _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ), + $pt->labels->name, + $pt->name + ) + ) + ); + } + } + ?> +
    +

    + taxonomies ) ) { + ?> +

    +
      + taxonomies as $taxonomy ) { + $tax = get_taxonomy( $taxonomy ); + if ( ! empty( $tax ) ) { + $disabled = in_array( $taxonomy, $this->disabled_taxonomies ); + printf( + '
    • ', + esc_attr( $taxonomy ), + checked( $disabled || in_array( $taxonomy, $this->options['taxonomies'], true ), true, false ), + disabled( $disabled, true, false ), + esc_html( + sprintf( + /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */ + _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ), + $tax->labels->name, + $tax->name + ) + ) + ); + } + } + ?> +
    +

    + 'licenses', + 'title' => __( 'License keys', 'polylang' ), + 'description' => __( 'Manage licenses for Polylang Pro and add-ons.', 'polylang' ), + ) + ); + + $this->buttons['cancel'] = sprintf( '', __( 'Close', 'polylang' ) ); + + $this->items = apply_filters( 'pll_settings_licenses', array() ); + + add_action( 'wp_ajax_pll_deactivate_license', array( $this, 'deactivate_license' ) ); + } + + /** + * Tells if the module is active + * + * @since 1.9 + * + * @return bool + */ + public function is_active() { + return ! empty( $this->items ); + } + + /** + * Displays the settings form + * + * @since 1.9 + */ + protected function form() { + if ( ! empty( $this->items ) ) { ?> + + items as $item ) { + echo $this->get_row( $item ); // phpcs:ignore WordPress.Security.EscapeOutput + } + ?> +
    + get_form_field(); + } + + /** + * Ajax method to save the license keys and activate the licenses at the same time + * Overrides parent's method + * + * @since 1.9 + */ + public function save_options() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] && ! empty( $_POST['licenses'] ) ) { + $x = new WP_Ajax_Response(); + foreach ( $this->items as $item ) { + if ( ! empty( $_POST['licenses'][ $item->id ] ) ) { + $updated_item = $item->activate_license( sanitize_key( $_POST['licenses'][ $item->id ] ) ); + $x->Add( array( 'what' => 'license-update', 'data' => $item->id, 'supplemental' => array( 'html' => $this->get_row( $updated_item ) ) ) ); + } + } + + // Updated message + pll_add_notice( new WP_Error( 'settings_updated', __( 'Settings saved.', 'polylang' ), 'success' ) ); + ob_start(); + settings_errors( 'polylang' ); + $x->Add( array( 'what' => 'success', 'data' => ob_get_clean() ) ); + $x->send(); + } + } + + /** + * Ajax method to deactivate a license + * + * @since 1.9 + * + * @return void + */ + public function deactivate_license() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( ! isset( $_POST['id'] ) ) { + wp_die( 0 ); + } + + $id = substr( sanitize_text_field( wp_unslash( $_POST['id'] ) ), 11 ); + wp_send_json( + array( + 'id' => $id, + 'html' => $this->get_row( $this->items[ $id ]->deactivate_license() ), + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/settings-media.php b/wp-content/plugins/polylang/settings/settings-media.php new file mode 100644 index 0000000..1f7ea61 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-media.php @@ -0,0 +1,37 @@ + 'media', + 'title' => __( 'Media', 'polylang' ), + 'description' => __( 'Activate languages and translations for media only if you need to translate the text attached to the media: the title, the alternative text, the caption, the description... Note that the file is not duplicated.', 'polylang' ), + 'active_option' => 'media_support', + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/settings-module.php b/wp-content/plugins/polylang/settings/settings-module.php new file mode 100644 index 0000000..3e5767d --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-module.php @@ -0,0 +1,375 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + $this->links_model = &$polylang->links_model; + + $args = wp_parse_args( + $args, + array( + 'title' => '', + 'description' => '', + 'active_option' => 'none', + ) + ); + + if ( empty( $args['active_option'] ) ) { + // Backward compatibility. + $args['active_option'] = 'none'; + } + + foreach ( $args as $prop => $value ) { + $this->$prop = $value; + } + + // All possible action links, even if not always a link ;-) + $this->action_links = array( + 'configure' => sprintf( + '%s', + esc_attr__( 'Configure this module', 'polylang' ), + '#', + esc_html__( 'Settings', 'polylang' ) + ), + 'deactivate' => sprintf( + '%s', + esc_attr__( 'Deactivate this module', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=deactivate&noheader=true&module=' . $this->module, 'pll_deactivate' ) ), + esc_html__( 'Deactivate', 'polylang' ) + ), + 'activate' => sprintf( + '%s', + esc_attr__( 'Activate this module', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=activate&noheader=true&module=' . $this->module, 'pll_activate' ) ), + esc_html__( 'Activate', 'polylang' ) + ), + 'activated' => esc_html__( 'Activated', 'polylang' ), + 'deactivated' => esc_html__( 'Deactivated', 'polylang' ), + ); + + $this->buttons = array( + 'cancel' => sprintf( '', esc_html__( 'Cancel', 'polylang' ) ), + 'save' => sprintf( '', esc_html__( 'Save Changes', 'polylang' ) ), + ); + + // Ajax action to save options. + add_action( 'wp_ajax_pll_save_options', array( $this, 'save_options' ) ); + } + + /** + * Tells if the module is active. + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return 'none' === $this->active_option || ( 'preview' !== $this->active_option && ! empty( $this->options[ $this->active_option ] ) ); + } + + /** + * Activates the module. + * + * @since 1.8 + * + * @return void + */ + public function activate() { + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $this->options[ $this->active_option ] = true; + } + } + + /** + * Deactivates the module. + * + * @since 1.8 + * + * @return void + */ + public function deactivate() { + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $this->options[ $this->active_option ] = false; + } + } + + /** + * Protected method to display a configuration form. + * + * @since 1.8 + * + * @return void + */ + protected function form() { + // Child classes can provide a form. + } + + /** + * Public method returning the form if any. + * + * @since 1.8 + * + * @return string + */ + public function get_form() { + if ( ! $this->is_active() ) { + return ''; + } + + // Read the form only once + if ( false === $this->form ) { + ob_start(); + $this->form(); + $this->form = ob_get_clean(); + } + + return $this->form; + } + + /** + * Allows child classes to prepare the received data before saving. + * + * @since 3.7 + * + * @param array $options Raw values to save. + * @return array + */ + protected function prepare_raw_data( array $options ): array { + return $options; + } + + /** + * Ajax method to save the options. + * + * @since 1.8 + * + * @return void + */ + public function save_options() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] ) { + // It's up to the child class to decide which options are saved, whether there are errors or not + $posted_options = array_diff_key( $_POST, array_flip( array( 'action', 'module', 'pll_ajax_backend', 'pll_ajax_settings', '_pll_nonce' ) ) ); + $errors = $this->options->merge( $this->prepare_raw_data( $posted_options ) ); + + // Refresh language cache in case home urls have been modified + $this->model->clean_languages_cache(); + + // Refresh rewrite rules in case rewrite, hide_default, post types or taxonomies options have been modified + // Don't use flush_rewrite_rules as we don't have the right links model and permastruct + delete_option( 'rewrite_rules' ); + + ob_start(); + + if ( ! $errors->has_errors() ) { + // Send update message + pll_add_notice( new WP_Error( 'settings_updated', __( 'Settings saved.', 'polylang' ), 'success' ) ); + settings_errors( 'polylang' ); + $x = new WP_Ajax_Response( array( 'what' => 'success', 'data' => ob_get_clean() ) ); + $x->send(); + } else { + // Send error messages + pll_add_notice( $errors ); + settings_errors( 'polylang' ); + $x = new WP_Ajax_Response( array( 'what' => 'error', 'data' => ob_get_clean() ) ); + $x->send(); + } + } + } + + /** + * Get the row actions. + * + * @since 1.8 + * + * @return string[] + */ + protected function get_actions() { + $actions = array(); + + if ( $this->is_active() && $this->get_form() ) { + $actions[] = 'configure'; + } + + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $actions[] = $this->is_active() ? 'deactivate' : 'activate'; + } + + if ( empty( $actions ) ) { + $actions[] = $this->is_active() ? 'activated' : 'deactivated'; + } + + return $actions; + } + + /** + * Get the actions links. + * + * @since 1.8 + * + * @return string[] Action links. + */ + public function get_action_links() { + return array_intersect_key( $this->action_links, array_flip( $this->get_actions() ) ); + } + + /** + * Default upgrade message (to Pro version). + * + * @since 1.9 + * + * @return string + */ + protected function default_upgrade_message() { + return sprintf( + '%s %s', + __( 'To enable this feature, you need Polylang Pro.', 'polylang' ), + 'https://polylang.pro', + __( 'Upgrade now.', 'polylang' ) + ); + } + + /** + * Allows child classes to display an upgrade message. + * + * @since 1.9 + * + * @return string + */ + public function get_upgrade_message() { + return 'preview' === $this->active_option ? $this->default_upgrade_message() : ''; + } + + /** + * Get the buttons. + * + * @since 1.9 + * + * @return string[] An array of html fragment for the buttons. + */ + public function get_buttons() { + return $this->buttons; + } +} diff --git a/wp-content/plugins/polylang/settings/settings-url.php b/wp-content/plugins/polylang/settings/settings-url.php new file mode 100644 index 0000000..2854fb0 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-url.php @@ -0,0 +1,279 @@ + 'url', + 'title' => __( 'URL modifications', 'polylang' ), + 'description' => __( 'Decide how your URLs will look like.', 'polylang' ), + ) + ); + + $this->page_on_front = &$polylang->static_pages->page_on_front; + } + + /** + * Displays the fieldset to choose how the language is set + * + * @since 1.8 + * + * @return void + */ + protected function force_lang() { + if ( 'yes' === get_option( 'pll_language_from_content_available' ) ) { + ?> +

    + +

    + +

    + + +

    ' . esc_html( home_url( $this->links_model->using_permalinks ? 'en/my-post/' : '?lang=en&p=1' ) ) . ''; ?>

    + +

    ' . esc_html( str_replace( array( '://', 'www.' ), array( '://en.', '' ), home_url( 'my-post/' ) ) ) . ''; ?>

    + + options['force_lang'] ? '' : 'style="display: none;"'; ?>> + model->get_languages_list() as $lg ) { + $url = $this->options['domains'][ $lg->slug ] ?? ( $lg->is_default ? $this->links_model->home : '' ); + printf( + '' . + '', + esc_attr( $lg->slug ), + esc_attr( $lg->name ), + esc_url( $url ) + ); + } + ?> +
    + + + + +

    ' . esc_html( home_url( 'en/' ) ) . ''; ?>

    + +

    ' . esc_html( home_url( 'language/en/' ) ) . ''; ?>

    + + +

    + model->post->get_language( $this->page_on_front ); + /** @var PLL_Language $lang */ + $lang = $lang ?: $this->model->get_default_language(); + printf( + /* translators: %1$s example url when the option is active. %2$s example url when the option is not active */ + esc_html__( 'Example: %1$s instead of %2$s', 'polylang' ), + '' . esc_html( $this->links_model->home_url( $lang ) ) . '', + '' . esc_html( _get_page_link( $this->page_on_front ) ) . '' + ); + ?> +

    + +
    +
    + force_lang(); ?> +
    +
    + +
    +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + hide_default(); ?> +
    + links_model->using_permalinks ) { + ?> +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + rewrite(); ?> +
    + page_on_front ) { + ?> +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + redirect_lang(); ?> +
    + +
    + 0, + 'domains' => array(), + 'hide_default' => 0, + 'rewrite' => 0, + 'redirect_lang' => 0, + ); + + return array_intersect_key( array_merge( $defaults, $options ), $defaults ); // Take care to return only validated options + } +} diff --git a/wp-content/plugins/polylang/settings/settings.php b/wp-content/plugins/polylang/settings/settings.php new file mode 100644 index 0000000..7f06225 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings.php @@ -0,0 +1,422 @@ +active_tab = 'mlang' === $_GET['page'] ? 'lang' : substr( sanitize_key( $_GET['page'] ), 6 ); // phpcs:ignore WordPress.Security.NonceVerification + } + + PLL_Admin_Strings::init(); + + add_action( 'admin_init', array( $this, 'register_settings_modules' ) ); + + // Adds screen options and the about box in the languages admin panel. + add_action( 'load-toplevel_page_mlang', array( $this, 'load_page' ) ); + add_action( 'load-languages_page_mlang_strings', array( $this, 'load_page_strings' ) ); + + // Saves the per-page value in screen options. + add_filter( 'set_screen_option_pll_lang_per_page', array( $this, 'set_screen_option' ), 10, 3 ); + add_filter( 'set_screen_option_pll_strings_per_page', array( $this, 'set_screen_option' ), 10, 3 ); + } + + /** + * Initializes the modules + * + * @since 1.8 + * + * @return void + */ + public function register_settings_modules() { + $modules = array(); + + if ( $this->model->has_languages() ) { + $modules = array( + 'PLL_Settings_Url', + 'PLL_Settings_Browser', + 'PLL_Settings_Media', + 'PLL_Settings_CPT', + ); + } + + $modules[] = 'PLL_Settings_Licenses'; + + /** + * Filter the list of setting modules + * + * @since 1.8 + * + * @param array $modules the list of module classes + */ + $modules = apply_filters( 'pll_settings_modules', $modules ); + + foreach ( $modules as $key => $class ) { + $key = is_numeric( $key ) ? strtolower( str_replace( 'PLL_Settings_', '', $class ) ) : $key; + $this->modules[ $key ] = new $class( $this ); + } + } + + /** + * Loads the about metabox + * + * @since 0.8 + * + * @return void + */ + public function metabox_about() { + include __DIR__ . '/view-about.php'; + } + + /** + * Adds screen options and the about box in the languages admin panel + * + * @since 0.9.5 + * + * @return void + */ + public function load_page() { + if ( ! defined( 'PLL_DISPLAY_ABOUT' ) || PLL_DISPLAY_ABOUT ) { + add_meta_box( + 'pll-about-box', + __( 'About Polylang', 'polylang' ), + array( $this, 'metabox_about' ), + 'toplevel_page_mlang', + 'normal' + ); + } + + add_screen_option( + 'per_page', + array( + 'label' => __( 'Languages', 'polylang' ), + 'default' => 10, + 'option' => 'pll_lang_per_page', + ) + ); + + add_action( 'admin_notices', array( $this, 'notice_objects_with_no_lang' ) ); + } + + /** + * Adds screen options in the strings translations admin panel + * + * @since 2.1 + * + * @return void + */ + public function load_page_strings() { + add_screen_option( + 'per_page', + array( + 'label' => __( 'Strings translations', 'polylang' ), + 'default' => 10, + 'option' => 'pll_strings_per_page', + ) + ); + } + + /** + * Saves the number of rows in the languages or strings table set by this user. + * + * @since 0.9.5 + * + * @param mixed $screen_option False or value returned by a previous filter, not used. + * @param string $option The name of the option, not used. + * @param int $value The new value of the option to save. + * @return int The new value of the option. + */ + public function set_screen_option( $screen_option, $option, $value ) { + return (int) $value; + } + + /** + * Manages the user input for the languages pages. + * + * @since 1.9 + * + * @param string $action The action name. + * @return void + */ + public function handle_actions( $action ) { + switch ( $action ) { + case 'add': + check_admin_referer( 'add-lang', '_wpnonce_add-lang' ); + $errors = $this->model->add_language( $_POST ); + + if ( is_wp_error( $errors ) ) { + pll_add_notice( $errors ); + } else { + pll_add_notice( new WP_Error( 'pll_languages_created', __( 'Language added.', 'polylang' ), 'success' ) ); + $locale = sanitize_locale_name( $_POST['locale'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + + if ( 'en_US' !== $locale && current_user_can( 'install_languages' ) ) { + // Attempts to install the language pack + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + if ( ! wp_download_language_pack( $locale ) ) { + pll_add_notice( new WP_Error( 'pll_download_mo', __( 'The language was created, but the WordPress language file was not downloaded. Please install it manually.', 'polylang' ), 'warning' ) ); + } + + // Force checking for themes and plugins translations updates + wp_clean_themes_cache(); + wp_clean_plugins_cache(); + } + } + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'delete': + check_admin_referer( 'delete-lang' ); + + if ( ! empty( $_GET['lang'] ) && $this->model->delete_language( (int) $_GET['lang'] ) ) { + pll_add_notice( new WP_Error( 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'success' ) ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'update': + check_admin_referer( 'add-lang', '_wpnonce_add-lang' ); + $errors = $this->model->update_language( $_POST ); + + if ( is_wp_error( $errors ) ) { + pll_add_notice( $errors ); + } else { + pll_add_notice( new WP_Error( 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'success' ) ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'default-lang': + check_admin_referer( 'default-lang' ); + + if ( $lang = $this->model->get_language( (int) $_GET['lang'] ) ) { + $this->model->update_default_lang( $lang->slug ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'content-default-lang': + check_admin_referer( 'content-default-lang' ); + + $this->model->set_language_in_mass(); + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'activate': + check_admin_referer( 'pll_activate' ); + if ( isset( $_GET['module'] ) ) { + $module = sanitize_key( $_GET['module'] ); + if ( isset( $this->modules[ $module ] ) ) { + $this->modules[ $module ]->activate(); + } + } + self::redirect(); + break; + + case 'deactivate': + check_admin_referer( 'pll_deactivate' ); + if ( isset( $_GET['module'] ) ) { + $module = sanitize_key( $_GET['module'] ); + if ( isset( $this->modules[ $module ] ) ) { + $this->modules[ $module ]->deactivate(); + } + } + self::redirect(); + break; + + default: + /** + * Fires when a non default action has been sent to Polylang settings + * + * @since 1.8 + */ + do_action( "mlang_action_$action" ); + break; + } + } + + /** + * Displays the 3 tabs pages: languages, strings translations, settings + * Also manages user input for these pages + * + * @since 0.1 + * + * @return void + */ + public function languages_page() { + switch ( $this->active_tab ) { + case 'lang': + // Prepare the list table of languages + $list_table = new PLL_Table_Languages(); + $list_table->prepare_items( $this->model->get_languages_list() ); + break; + + case 'strings': + $string_table = new PLL_Table_String( $this->model->get_languages_list() ); + $string_table->prepare_items(); + break; + } + + // Handle user input + $action = isset( $_REQUEST['pll_action'] ) ? sanitize_key( $_REQUEST['pll_action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + if ( 'edit' === $action && ! empty( $_GET['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // phpcs:ignore WordPress.Security.NonceVerification, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $edit_lang = $this->model->get_language( (int) $_GET['lang'] ); + } else { + $this->handle_actions( $action ); + } + + // Displays the page + $modules = $this->modules; + $active_tab = $this->active_tab; + include __DIR__ . '/view-languages.php'; + } + + /** + * Enqueues scripts and styles + * + * @return void + */ + public function admin_enqueue_scripts() { + parent::admin_enqueue_scripts(); + + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + wp_enqueue_script( 'pll_settings', plugins_url( '/js/build/settings' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'wp-ajax-response', 'postbox', 'jquery-ui-selectmenu', 'wp-hooks' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_settings', 'pll_settings', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + + wp_enqueue_style( 'pll_selectmenu', plugins_url( '/css/build/selectmenu' . $suffix . '.css', POLYLANG_ROOT_FILE ), array(), POLYLANG_VERSION ); + } + + /** + * Displays a notice when there are objects with no language assigned + * + * @since 1.8 + * + * @return void + */ + public function notice_objects_with_no_lang() { + if ( ! empty( $this->options['default_lang'] ) && $this->model->get_objects_with_no_lang( 1 ) ) { + printf( + '

    %s %s

    ', + esc_html__( 'There are posts, pages, categories or tags without language.', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&pll_action=content-default-lang&noheader=true', 'content-default-lang' ) ), + esc_html__( 'You can set them all to the default language.', 'polylang' ) + ); + } + } + + /** + * Redirects to language page ( current active tab ) + * saves error messages in a transient for reuse in redirected page + * + * @since 1.5 + * + * @param array $args query arguments to add to the url + * @return void + */ + public static function redirect( $args = array() ) { + $errors = get_settings_errors( 'polylang' ); + if ( ! empty( $errors ) ) { + set_transient( 'settings_errors', $errors, 30 ); + $args['settings-updated'] = 1; + } + // Remove possible 'pll_action' and 'lang' query args from the referer before redirecting + wp_safe_redirect( add_query_arg( $args, remove_query_arg( array( 'pll_action', 'lang' ), wp_get_referer() ) ) ); + exit; + } + + /** + * Get the list of predefined languages + * + * @since 2.3 + * + * @return string[][] { + * An array of array of language properties. + * + * @type string[] { + * @type string $code ISO 639-1 language code. + * @type string $locale WordPress locale. + * @type string $name Native language name. + * @type string $dir Text direction: 'ltr' or 'rtl'. + * @type string $flag Flag code, generally the country code. + * @type string $w3c W3C locale. + * @type string $facebook Facebook locale. + * } + * } + */ + public static function get_predefined_languages() { + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + + $languages = include __DIR__ . '/languages.php'; + $translations = wp_get_available_translations(); + + // Keep only languages with existing WP language pack + // Unless the transient has expired and we don't have an internet connection to refresh it + if ( ! empty( $translations ) ) { + $translations['en_US'] = ''; // Languages packs don't include en_US + $languages = array_intersect_key( $languages, $translations ); + } + + /** + * Filter the list of predefined languages + * + * @since 1.7.10 + * @since 2.3 The languages arrays use associative keys instead of numerical keys + * @see https://github.com/polylang/polylang/blob/2.8.2/settings/languages.php the list of predefined languages + * + * @param array $languages + */ + $languages = apply_filters( 'pll_predefined_languages', $languages ); + + // Keep only languages with all necessary information + foreach ( $languages as $k => $lang ) { + if ( ! isset( $lang['code'], $lang['locale'], $lang['name'], $lang['dir'], $lang['flag'] ) ) { + unset( $languages[ $k ] ); + } + } + + return $languages; + } +} diff --git a/wp-content/plugins/polylang/settings/table-languages.php b/wp-content/plugins/polylang/settings/table-languages.php new file mode 100644 index 0000000..c27b5b9 --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-languages.php @@ -0,0 +1,277 @@ + 'Languages', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + } + + /** + * Generates content for a single row of the table. + * + * @since 1.8 + * + * @param PLL_Language $item The language item. + * @return void + */ + public function single_row( $item ) { + /** + * Filter the list of classes assigned a row in the languages list table + * + * @since 1.8 + * + * @param array $classes The list of class names. + * @param PLL_Language $item The language item. + */ + $classes = apply_filters( 'pll_languages_row_classes', array(), $item ); + echo '' : ' class="' . esc_attr( implode( ' ', $classes ) ) . '">' ); + $this->single_row_columns( $item ); + echo ''; + } + + /** + * Displays the item information in a column ( default case ). + * + * @since 0.1 + * + * @param PLL_Language $item The language item. + * @param string $column_name The column name. + * @return string|int + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'locale': + case 'slug': + return esc_html( $item->$column_name ); + + case 'term_group': + return (int) $item->$column_name; + + case 'count': + return $item->get_tax_prop( 'language', $column_name ); + + default: + return $item->$column_name; // Flag. + } + } + + /** + * Displays the item information in the column 'name' + * Displays the edit and delete action links + * + * @since 0.1 + * + * @param PLL_Language $item The language item. + * @return string + */ + public function column_name( $item ) { + return sprintf( + '%s', + esc_attr__( 'Edit this language', 'polylang' ), + esc_url( admin_url( 'admin.php?page=mlang&pll_action=edit&lang=' . $item->term_id ) ), + esc_html( $item->name ) + ); + } + + /** + * Displays the item information in the default language + * Displays the 'make default' action link + * + * @since 1.8 + * + * @param PLL_Language $item The language item. + * @return string + */ + public function column_default_lang( $item ) { + if ( ! $item->is_default ) { + $s = sprintf( + '
    + %3$s +
    ', + esc_attr__( 'Select as default language', 'polylang' ), + wp_nonce_url( '?page=mlang&pll_action=default-lang&noheader=true&lang=' . $item->term_id, 'default-lang' ), + /* translators: accessibility text, %s is a native language name */ + esc_html( sprintf( __( 'Choose %s as default language', 'polylang' ), $item->name ) ) + ); + + /** + * Filters the default language row action in the languages list table. + * + * @since 1.8 + * + * @param string $s The html markup of the action. + * @param PLL_Language $item The language item. + */ + $s = apply_filters( 'pll_default_lang_row_action', $s, $item ); + } else { + $s = sprintf( + '%1$s', + /* translators: accessibility text */ + esc_html__( 'Default language', 'polylang' ) + ); + } + + return $s; + } + + /** + * Gets the list of columns + * + * @since 0.1 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'name' => esc_html__( 'Full name', 'polylang' ), + 'locale' => esc_html__( 'Locale', 'polylang' ), + 'slug' => esc_html__( 'Code', 'polylang' ), + 'default_lang' => sprintf( '%2$s', esc_attr__( 'Default language', 'polylang' ), esc_html__( 'Default language', 'polylang' ) ), + 'term_group' => esc_html__( 'Order', 'polylang' ), + 'flag' => esc_html__( 'Flag', 'polylang' ), + 'count' => esc_html__( 'Posts', 'polylang' ), + ); + } + + /** + * Gets the list of sortable columns + * + * @since 0.1 + * + * @return array + */ + public function get_sortable_columns() { + return array( + 'name' => array( 'name', true ), // sorted by name by default + 'locale' => array( 'locale', false ), + 'slug' => array( 'slug', false ), + 'term_group' => array( 'term_group', false ), + 'count' => array( 'count', false ), + ); + } + + /** + * Gets the name of the default primary column. + * + * @since 2.1 + * + * @return string Name of the default primary column, in this case, 'name'. + */ + protected function get_default_primary_column_name() { + return 'name'; + } + + /** + * Generates and display row actions links for the list table. + * + * @since 1.8 + * + * @param PLL_Language $item The language item being acted upon. + * @param string $column_name Current column name. + * @param string $primary Primary column name. + * @return string The row actions output. + */ + protected function handle_row_actions( $item, $column_name, $primary ) { + if ( $primary !== $column_name ) { + return ''; + } + + $actions = array( + 'edit' => sprintf( + '%s', + esc_attr__( 'Edit this language', 'polylang' ), + esc_url( admin_url( 'admin.php?page=mlang&pll_action=edit&lang=' . $item->term_id ) ), + esc_html__( 'Edit', 'polylang' ) + ), + 'delete' => sprintf( + '%s', + esc_attr__( 'Delete this language and all its associated data', 'polylang' ), + wp_nonce_url( '?page=mlang&pll_action=delete&noheader=true&lang=' . $item->term_id, 'delete-lang' ), + esc_js( __( 'You are about to permanently delete this language. Are you sure?', 'polylang' ) ), + esc_html__( 'Delete', 'polylang' ) + ), + ); + + /** + * Filters the list of row actions in the languages list table. + * + * @since 1.8 + * + * @param array $actions A list of html markup actions. + * @param PLL_Language $item The language item. + */ + $actions = apply_filters( 'pll_languages_row_actions', $actions, $item ); + + return $this->row_actions( $actions ); + } + + /** + * Sorts language items. + * + * @since 0.1 + * + * @param PLL_Language $a The first language to compare. + * @param PLL_Language $b The second language to compare. + * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b. + */ + protected function usort_reorder( $a, $b ) { + $orderby = ! empty( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification + // Determine sort order + if ( is_numeric( $a->$orderby ) ) { + $result = $a->$orderby > $b->$orderby ? 1 : -1; + } else { + $result = strcmp( $a->$orderby, $b->$orderby ); + } + // Send final sort direction to usort. + return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Prepares the list of languages for display. + * + * @since 0.1 + * + * @param PLL_Language[] $data The list of languages. + * @return void + */ + public function prepare_items( $data = array() ) { + $per_page = $this->get_items_per_page( 'pll_lang_per_page' ); + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); + + usort( $data, array( $this, 'usort_reorder' ) ); + + $total_items = count( $data ); + $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page ); + + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => (int) ceil( $total_items / $per_page ), + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/table-settings.php b/wp-content/plugins/polylang/settings/table-settings.php new file mode 100644 index 0000000..e11a89e --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-settings.php @@ -0,0 +1,194 @@ + 'Settings', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + } + + /** + * Get the table classes for styling + * + * @since 1.8 + */ + protected function get_table_classes() { + return array( 'wp-list-table', 'widefat', 'plugins', 'pll-settings' ); // get the style of the plugins list table + one specific class + } + + /** + * Displays a single row. + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @return void + */ + public function single_row( $item ) { + // Classes to reuse css from the plugins list table + $classes = $item->is_active() ? 'active' : 'inactive'; + if ( $message = $item->get_upgrade_message() ) { + $classes .= ' update'; + } + + // Display the columns + printf( '', esc_attr( $item->module ), esc_attr( $classes ) ); + $this->single_row_columns( $item ); + echo ''; + + // Display an upgrade message if there is any, reusing css from the plugins updates + if ( $message = $item->get_upgrade_message() ) { + printf( + ' + %s + ', + sprintf( '

    %s

    ', $message ) // phpcs:ignore WordPress.Security.EscapeOutput + ); + } + + // The settings if there are + // "inactive" class to reuse css from the plugins list table + if ( $form = $item->get_form() ) { + printf( + ' + + %s + %s +

    + %s +

    + + ', + esc_attr( $item->module ), + esc_html( $item->title ), + $form, // phpcs:ignore + implode( $item->get_buttons() ) // phpcs:ignore + ); + } + } + + /** + * Generates the columns for a single row of the table. + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @return void + */ + protected function single_row_columns( $item ) { + $column_info = $this->get_column_info(); + $columns = $column_info[0]; + $primary = $column_info[3]; + + foreach ( array_keys( $columns ) as $column_name ) { + $classes = "$column_name column-$column_name"; + if ( $primary === $column_name ) { + $classes .= ' column-primary'; + } + + if ( 'cb' == $column_name ) { + echo ''; + echo $this->column_cb( $item ); // phpcs:ignore WordPress.Security.EscapeOutput + echo ''; + } else { + printf( '', esc_attr( $classes ) ); + echo $this->column_default( $item, $column_name ); // phpcs:ignore WordPress.Security.EscapeOutput + echo ''; + } + } + } + + /** + * Displays the item information in a column (default case). + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @param string $column_name Column name. + * @return string The column name. + */ + protected function column_default( $item, $column_name ) { + if ( 'plugin-title' == $column_name ) { + return sprintf( '%s', esc_html( $item->title ) ) . $this->row_actions( $item->get_action_links(), true /*always visible*/ ); + } + return $item->$column_name; + } + + /** + * Gets the list of columns. + * + * @since 1.8 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'cb' => '', // For the 4px border inherited from plugins when the module is activated + 'plugin-title' => esc_html__( 'Module', 'polylang' ), // plugin-title for styling + 'description' => esc_html__( 'Description', 'polylang' ), + ); + } + + /** + * Gets the name of the primary column. + * + * @since 1.8 + * + * @return string The name of the primary column. + */ + protected function get_primary_column_name() { + return 'plugin-title'; + } + + /** + * Prepares the list of items for displaying + * + * @since 1.8 + * + * @param PLL_Settings_Module[] $items Array of settings module items. + * @return void + */ + public function prepare_items( $items = array() ) { + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns(), $this->get_primary_column_name() ); + + // Sort rows, lowest priority on top. + usort( + $items, + function ( $a, $b ) { + return $a->priority > $b->priority ? 1 : -1; + } + ); + $this->items = $items; + } + + /** + * Avoids displaying an empty tablenav + * + * @since 2.1 + * + * @param string $which 'top' or 'bottom' + * @return void + */ + protected function display_tablenav( $which ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable +} diff --git a/wp-content/plugins/polylang/settings/table-string.php b/wp-content/plugins/polylang/settings/table-string.php new file mode 100644 index 0000000..74bd621 --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-string.php @@ -0,0 +1,453 @@ + 'Strings translations', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + + $this->languages = $languages; + $this->strings = PLL_Admin_Strings::get_strings(); + $this->groups = array_unique( wp_list_pluck( $this->strings, 'context' ) ); + + $this->selected_group = -1; + + if ( ! empty( $_GET['group'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $group = sanitize_text_field( wp_unslash( $_GET['group'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + if ( in_array( $group, $this->groups ) ) { + $this->selected_group = $group; + } + } + + add_action( 'mlang_action_string-translation', array( $this, 'save_translations' ) ); + } + + /** + * Displays the item information in a column (default case). + * + * @since 0.6 + * + * @param array $item Data related to the current string. + * @param string $column_name The current column name. + * @return string + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Displays the checkbox in first column. + * + * @since 1.1 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_cb( $item ) { + return sprintf( + '', + esc_attr( $item['row'] ), + /* translators: accessibility text, %s is a string potentially in any language */ + sprintf( __( 'Select %s', 'polylang' ), format_to_edit( $item['string'] ) ), + empty( $item['icl'] ) ? 'disabled' : '' // Only strings registered with WPML API can be removed. + ); + } + + /** + * Displays the string to translate. + * + * @since 1.0 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_string( $item ) { + return format_to_edit( $item['string'] ); // Don't interpret special chars for the string column. + } + + /** + * Displays the translations to edit. + * + * @since 0.6 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_translations( $item ) { + $out = ''; + $languages = array(); + + foreach ( $this->languages as $language ) { + $languages[ $language->slug ] = $language->name; + } + + foreach ( $item['translations'] as $key => $translation ) { + $input_type = $item['multiline'] ? + '' : + ''; + $out .= sprintf( + '
    ' . $input_type . '
    ' . "\n", + esc_attr( $key ), + esc_attr( $item['row'] ), + esc_html( $languages[ $key ] ), + format_to_edit( $translation ) // Don't interpret special chars. + ); + } + + return $out; + } + + /** + * Gets the list of columns. + * + * @since 0.6 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'cb' => '', // Checkbox. + 'string' => esc_html__( 'String', 'polylang' ), + 'name' => esc_html__( 'Name', 'polylang' ), + 'context' => esc_html__( 'Group', 'polylang' ), + 'translations' => esc_html__( 'Translations', 'polylang' ), + ); + } + + /** + * Gets the list of sortable columns + * + * @since 0.6 + * + * @return array + */ + public function get_sortable_columns() { + return array( + 'string' => array( 'string', false ), + 'name' => array( 'name', false ), + 'context' => array( 'context', false ), + ); + } + + /** + * Gets the name of the default primary column. + * + * @since 2.1 + * + * @return string Name of the default primary column, in this case, 'string'. + */ + protected function get_default_primary_column_name() { + return 'string'; + } + + /** + * Search for a string in translations. Case insensitive. + * + * @since 2.6 + * + * @param PLL_Language[] $languages An array of language objects. + * @param string $s Searched string. + * @return string[] Found strings. + */ + protected function search_in_translations( $languages, $s ) { + $founds = array(); + + foreach ( $languages as $language ) { + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + foreach ( wp_list_pluck( $mo->entries, 'translations' ) as $string => $translation ) { + if ( false !== stripos( $translation[0], $s ) ) { + $founds[] = $string; + } + } + } + + return array_unique( $founds ); + } + + /** + * Sorts registered string items. + * + * @since 0.6 + * + * @param array $a The first item to compare. + * @param array $b The second item to compare. + * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b. + */ + protected function usort_reorder( $a, $b ) { + if ( ! empty( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $orderby = sanitize_key( $_GET['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification + if ( isset( $a[ $orderby ], $b[ $orderby ] ) ) { + $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order + return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification + } + } + + return 0; + } + + /** + * Prepares the list of registered strings for display. + * + * @since 0.6 + * + * @return void + */ + public function prepare_items() { + // Is admin language filter active? + if ( $lg = get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ) { + $languages = wp_list_filter( $this->languages, array( 'slug' => $lg ) ); + } else { + $languages = $this->languages; + } + + $data = $this->strings; + + // Filter by selected group + if ( -1 !== $this->selected_group ) { + $data = wp_list_filter( $data, array( 'context' => $this->selected_group ) ); + } + + // Filter by searched string + $s = empty( $_GET['s'] ) ? '' : wp_unslash( $_GET['s'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput + + if ( ! empty( $s ) ) { + // Search in translations + $in_translations = $this->search_in_translations( $languages, $s ); + + foreach ( $data as $key => $row ) { + if ( stripos( $row['name'], $s ) === false && stripos( $row['string'], $s ) === false && ! in_array( $row['string'], $in_translations ) ) { + unset( $data[ $key ] ); + } + } + } + + // Sorting + uasort( $data, array( $this, 'usort_reorder' ) ); + + // Paging + $per_page = $this->get_items_per_page( 'pll_strings_per_page' ); + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); + + $total_items = count( $data ); + $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page, true ); + + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => (int) ceil( $total_items / $per_page ), + ) + ); + + // Translate strings + // Kept for the end as it is a slow process + foreach ( $languages as $language ) { + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + foreach ( $this->items as $key => $row ) { + $this->items[ $key ]['translations'][ $language->slug ] = $mo->translate_if_any( $row['string'] ); + $this->items[ $key ]['row'] = $key; // Store the row number for convenience + } + } + } + + /** + * Get the list of possible bulk actions. + * + * @since 1.1 + * + * @return string[] Array of bulk actions. + */ + public function get_bulk_actions() { + return array( 'delete' => __( 'Delete', 'polylang' ) ); + } + + /** + * Get the current action selected from the bulk actions dropdown. + * overrides parent function to avoid submit button to trigger bulk actions + * + * @since 1.8 + * + * @return string|false The action name or False if no action was selected + */ + public function current_action() { + return empty( $_POST['submit'] ) ? parent::current_action() : false; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Displays the dropdown list to filter strings per group + * + * @since 1.1 + * + * @param string $which only 'top' is supported + * @return void + */ + public function extra_tablenav( $which ) { + if ( 'top' !== $which ) { + return; + } + + echo '
    '; + printf( + '', + /* translators: accessibility text */ + esc_html__( 'Filter by group', 'polylang' ) + ); + echo '' . "\n"; + + submit_button( __( 'Filter', 'polylang' ), 'button', 'filter_action', false, array( 'id' => 'post-query-submit' ) ); + echo '
    '; + } + + /** + * Saves the strings translations in DB + * Optionally clean the DB + * + * @since 1.9 + * + * @return void + */ + public function save_translations() { + check_admin_referer( 'string-translation', '_wpnonce_string-translation' ); + + if ( ! empty( $_POST['submit'] ) ) { + foreach ( $this->languages as $language ) { + if ( empty( $_POST['translation'][ $language->slug ] ) || ! is_array( $_POST['translation'][ $language->slug ] ) ) { // In case the language filter is active ( thanks to John P. Bloch ) + continue; + } + + $translations = array_map( 'trim', (array) wp_unslash( $_POST['translation'][ $language->slug ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + + foreach ( $translations as $key => $translation ) { + /** + * Filters the string translation before it is saved in DB. + * Allows to sanitize strings registered with pll_register_string(). + * + * @since 1.6 + * @since 2.7 The translation passed to the filter is unslashed. + * @since 3.7 Add original string as 4th parameter. + * + * @param string $translation The string translation. + * @param string $name The name as defined in pll_register_string. + * @param string $context The context as defined in pll_register_string. + * @param string $original The original string to translate. + */ + $translation = apply_filters( 'pll_sanitize_string_translation', $translation, $this->strings[ $key ]['name'], $this->strings[ $key ]['context'], $this->strings[ $key ]['string'] ); + $mo->add_entry( + $mo->make_entry( + $this->strings[ $key ]['string'], + $translation + ) + ); + } + + // Clean database ( removes all strings which were registered some day but are no more ) + if ( ! empty( $_POST['clean'] ) ) { + $new_mo = new PLL_MO(); + + foreach ( $this->strings as $string ) { + $new_mo->add_entry( $mo->make_entry( $string['string'], $mo->translate( $string['string'] ) ) ); + } + } + + isset( $new_mo ) ? $new_mo->export_to_db( $language ) : $mo->export_to_db( $language ); + } + + pll_add_notice( new WP_Error( 'pll_strings_translations_updated', __( 'Translations updated.', 'polylang' ), 'success' ) ); + + /** + * Fires after the strings translations are saved in DB + * + * @since 1.2 + */ + do_action( 'pll_save_strings_translations' ); + } + + // Unregisters strings registered through WPML API + if ( $this->current_action() === 'delete' && ! empty( $_POST['strings'] ) && function_exists( 'icl_unregister_string' ) ) { + foreach ( array_map( 'sanitize_key', $_POST['strings'] ) as $key ) { + icl_unregister_string( $this->strings[ $key ]['context'], $this->strings[ $key ]['name'] ); + } + } + + // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + $args = array_intersect_key( $_REQUEST, array_flip( array( 's', 'paged', 'group' ) ) ); + if ( ! empty( $_GET['paged'] ) && ! empty( $_POST['submit'] ) ) { + $args['paged'] = (int) $_GET['paged']; // Don't rely on $_REQUEST['paged'] or $_POST['paged']. See #14 + } + if ( ! empty( $args['s'] ) ) { + $args['s'] = urlencode( $args['s'] ); // Searched string needs to be encoded as it comes from $_POST + } + PLL_Settings::redirect( $args ); + } +} diff --git a/wp-content/plugins/polylang/settings/view-about.php b/wp-content/plugins/polylang/settings/view-about.php new file mode 100644 index 0000000..0772d57 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-about.php @@ -0,0 +1,29 @@ + +

    + ', + '' + ); + if ( ! defined( 'POLYLANG_PRO' ) ) { + echo ' '; + printf( + /* translators: %1$s is link start tag, %2$s is link end tag. */ + esc_html__( 'Support and extra features are available to %1$sPolylang Pro%2$s users.', 'polylang' ), + '', + '' + ); + } + ?> +

    diff --git a/wp-content/plugins/polylang/settings/view-languages.php b/wp-content/plugins/polylang/settings/view-languages.php new file mode 100644 index 0000000..1317fd2 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-languages.php @@ -0,0 +1,35 @@ + +
    +

    + +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-lang.php b/wp-content/plugins/polylang/settings/view-tab-lang.php new file mode 100644 index 0000000..3b7a07b --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-lang.php @@ -0,0 +1,178 @@ + +
    +
    +
    + display(); + ?> +
    + +
    +
    +
    + +
    +
    + +
    +

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

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->name ) : '' + ); + ?> +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->locale ) : '' + ); + ?> +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->slug ) : '' + ); + ?> +

    +
    + +
    + + %s', + checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, false, false ), + esc_html__( 'left to right', 'polylang' ) + ); + printf( + '', + checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, true, false ), + esc_html__( 'right to left', 'polylang' ) + ); + ?> +

    +
    + +
    + + +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->term_group ) : '' + ); + ?> +

    +
    + +
    +
    +
    +
    +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-settings.php b/wp-content/plugins/polylang/settings/view-tab-settings.php new file mode 100644 index 0000000..cc36cc0 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-settings.php @@ -0,0 +1,19 @@ + +
    + prepare_items( $modules ); + $list_table->display(); + ?> +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-strings.php b/wp-content/plugins/polylang/settings/view-tab-strings.php new file mode 100644 index 0000000..ab31a60 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-strings.php @@ -0,0 +1,33 @@ + +
    +
    + + search_box( __( 'Search translations', 'polylang' ), 'translations' ); + wp_nonce_field( 'string-translation', '_wpnonce_string-translation' ); + $string_table->display(); + printf( '
    ', esc_html__( 'Clean strings translation database', 'polylang' ) ); + ?> +

    + +
    +
    + +
    + +
    diff --git a/wp-content/plugins/polylang/uninstall.php b/wp-content/plugins/polylang/uninstall.php new file mode 100644 index 0000000..0b315e4 --- /dev/null +++ b/wp-content/plugins/polylang/uninstall.php @@ -0,0 +1,192 @@ +get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { + switch_to_blog( $blog_id ); + $this->uninstall(); + } + restore_current_blog(); + } else { + $this->uninstall(); + } + } + + /** + * Removes **all** plugin data. + * + * @since 0.5 + */ + public function uninstall() { + global $wpdb; + + do_action( 'pll_uninstall' ); + + // We need to register the taxonomies. + $pll_taxonomies = array( + 'language', + 'term_language', + 'post_translations', + 'term_translations', + ); + + foreach ( $pll_taxonomies as $taxonomy ) { + register_taxonomy( + $taxonomy, + null, + array( + 'label' => false, + 'public' => false, + 'query_var' => false, + 'rewrite' => false, + ) + ); + } + + $languages = get_terms( + array( + 'taxonomy' => 'language', + 'hide_empty' => false, + ) + ); + + // Delete users options. + delete_metadata( 'user', 0, 'pll_filter_content', '', true ); + delete_metadata( 'user', 0, 'pll_dismissed_notices', '', true ); // Legacy meta. + foreach ( $languages as $lang ) { + delete_metadata( 'user', 0, "description_{$lang->slug}", '', true ); + } + + // Delete menu language switchers. + $ids = get_posts( + array( + 'post_type' => 'nav_menu_item', + 'numberposts' => -1, + 'nopaging' => true, + 'fields' => 'ids', + 'meta_key' => '_pll_menu_item', + ) + ); + + foreach ( $ids as $id ) { + wp_delete_post( $id, true ); + } + + /* + * Backward compatibility with Polylang < 3.4. + * Delete the legacy strings translations. + */ + register_post_type( + 'polylang_mo', + array( + 'rewrite' => false, + 'query_var' => false, + ) + ); + $ids = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + 'fields' => 'ids', + ) + ); + foreach ( $ids as $id ) { + wp_delete_post( $id, true ); + } + + // Delete all what is related to languages and translations. + $term_ids = array(); + $tt_ids = array(); + + $terms = get_terms( + array( + 'taxonomy' => $pll_taxonomies, + 'hide_empty' => false, + ) + ); + + foreach ( $terms as $term ) { + $term_ids[] = (int) $term->term_id; + $tt_ids[] = (int) $term->term_taxonomy_id; + } + + if ( ! empty( $term_ids ) ) { + $term_ids = array_unique( $term_ids ); + $wpdb->query( + $wpdb->prepare( + sprintf( + "DELETE FROM {$wpdb->terms} WHERE term_id IN (%s)", + implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) + ), + $term_ids + ) + ); + $wpdb->query( + $wpdb->prepare( + sprintf( + "DELETE FROM {$wpdb->term_taxonomy} WHERE term_id IN (%s)", + implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) + ), + $term_ids + ) + ); + $wpdb->query( + $wpdb->prepare( + sprintf( + "DELETE FROM {$wpdb->termmeta} WHERE term_id IN (%s) AND meta_key='_pll_strings_translations'", + implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) + ), + $term_ids + ) + ); + } + + if ( ! empty( $tt_ids ) ) { + $tt_ids = array_unique( $tt_ids ); + $wpdb->query( $wpdb->prepare( sprintf( "DELETE FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (%s)", implode( ',', array_fill( 0, count( $tt_ids ), '%d' ) ) ), $tt_ids ) ); + } + + // Delete options. + delete_option( 'polylang' ); + delete_option( 'widget_polylang' ); // Automatically created by WP. + delete_option( 'polylang_wpml_strings' ); // Strings registered with icl_register_string. + delete_option( 'polylang_licenses' ); + delete_option( 'pll_dismissed_notices' ); + delete_option( 'pll_language_from_content_available' ); + + // Delete transients. + delete_transient( 'pll_languages_list' ); + } +} + +new PLL_Uninstall(); diff --git a/wp-content/plugins/polylang/vendor/autoload.php b/wp-content/plugins/polylang/vendor/autoload.php new file mode 100644 index 0000000..3ae9220 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/autoload.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php b/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..2052022 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/LICENSE b/wp-content/plugins/polylang/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php b/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..629dd14 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php @@ -0,0 +1,172 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'PLL_AS3CF' => $baseDir . '/integrations/wp-offload-media/as3cf.php', + 'PLL_Abstract_Sitemaps' => $baseDir . '/modules/sitemaps/abstract-sitemaps.php', + 'PLL_Accept_Language' => $baseDir . '/frontend/accept-language.php', + 'PLL_Accept_Languages_Collection' => $baseDir . '/frontend/accept-languages-collection.php', + 'PLL_Admin' => $baseDir . '/admin/admin.php', + 'PLL_Admin_Base' => $baseDir . '/admin/admin-base.php', + 'PLL_Admin_Block_Editor' => $baseDir . '/admin/admin-block-editor.php', + 'PLL_Admin_Classic_Editor' => $baseDir . '/admin/admin-classic-editor.php', + 'PLL_Admin_Default_Term' => $baseDir . '/admin/admin-default-term.php', + 'PLL_Admin_Filters' => $baseDir . '/admin/admin-filters.php', + 'PLL_Admin_Filters_Columns' => $baseDir . '/admin/admin-filters-columns.php', + 'PLL_Admin_Filters_Media' => $baseDir . '/admin/admin-filters-media.php', + 'PLL_Admin_Filters_Post' => $baseDir . '/admin/admin-filters-post.php', + 'PLL_Admin_Filters_Post_Base' => $baseDir . '/admin/admin-filters-post-base.php', + 'PLL_Admin_Filters_Term' => $baseDir . '/admin/admin-filters-term.php', + 'PLL_Admin_Filters_Widgets_Options' => $baseDir . '/admin/admin-filters-widgets-options.php', + 'PLL_Admin_Links' => $baseDir . '/admin/admin-links.php', + 'PLL_Admin_Model' => $baseDir . '/admin/admin-model.php', + 'PLL_Admin_Nav_Menu' => $baseDir . '/admin/admin-nav-menu.php', + 'PLL_Admin_Notices' => $baseDir . '/admin/admin-notices.php', + 'PLL_Admin_Site_Health' => $baseDir . '/modules/site-health/admin-site-health.php', + 'PLL_Admin_Static_Pages' => $baseDir . '/admin/admin-static-pages.php', + 'PLL_Admin_Strings' => $baseDir . '/admin/admin-strings.php', + 'PLL_Admin_Sync' => $baseDir . '/modules/sync/admin-sync.php', + 'PLL_Aqua_Resizer' => $baseDir . '/integrations/aqua-resizer/aqua-resizer.php', + 'PLL_Base' => $baseDir . '/include/base.php', + 'PLL_CRUD_Posts' => $baseDir . '/include/crud-posts.php', + 'PLL_CRUD_Terms' => $baseDir . '/include/crud-terms.php', + 'PLL_Cache' => $baseDir . '/include/cache.php', + 'PLL_Cache_Compat' => $baseDir . '/integrations/cache/cache-compat.php', + 'PLL_Canonical' => $baseDir . '/frontend/canonical.php', + 'PLL_Cft' => $baseDir . '/integrations/custom-field-template/cft.php', + 'PLL_Choose_Lang' => $baseDir . '/frontend/choose-lang.php', + 'PLL_Choose_Lang_Content' => $baseDir . '/frontend/choose-lang-content.php', + 'PLL_Choose_Lang_Domain' => $baseDir . '/frontend/choose-lang-domain.php', + 'PLL_Choose_Lang_Url' => $baseDir . '/frontend/choose-lang-url.php', + 'PLL_Cookie' => $baseDir . '/include/cookie.php', + 'PLL_Default_Term' => $baseDir . '/include/default-term.php', + 'PLL_Domain_Mapping' => $baseDir . '/integrations/domain-mapping/domain-mapping.php', + 'PLL_Duplicate_Post' => $baseDir . '/integrations/duplicate-post/duplicate-post.php', + 'PLL_Featured_Content' => $baseDir . '/integrations/jetpack/featured-content.php', + 'PLL_Filter_REST_Routes' => $baseDir . '/include/filter-rest-routes.php', + 'PLL_Filters' => $baseDir . '/include/filters.php', + 'PLL_Filters_Links' => $baseDir . '/include/filters-links.php', + 'PLL_Filters_Sanitization' => $baseDir . '/include/filters-sanitization.php', + 'PLL_Filters_Widgets_Options' => $baseDir . '/include/filters-widgets-options.php', + 'PLL_Format_Util' => $baseDir . '/include/format-util.php', + 'PLL_Frontend' => $baseDir . '/frontend/frontend.php', + 'PLL_Frontend_Auto_Translate' => $baseDir . '/frontend/frontend-auto-translate.php', + 'PLL_Frontend_Filters' => $baseDir . '/frontend/frontend-filters.php', + 'PLL_Frontend_Filters_Links' => $baseDir . '/frontend/frontend-filters-links.php', + 'PLL_Frontend_Filters_Search' => $baseDir . '/frontend/frontend-filters-search.php', + 'PLL_Frontend_Filters_Widgets' => $baseDir . '/frontend/frontend-filters-widgets.php', + 'PLL_Frontend_Links' => $baseDir . '/frontend/frontend-links.php', + 'PLL_Frontend_Nav_Menu' => $baseDir . '/frontend/frontend-nav-menu.php', + 'PLL_Frontend_Static_Pages' => $baseDir . '/frontend/frontend-static-pages.php', + 'PLL_Install' => $baseDir . '/install/install.php', + 'PLL_Install_Base' => $baseDir . '/install/install-base.php', + 'PLL_Integrations' => $baseDir . '/integrations/integrations.php', + 'PLL_Jetpack' => $baseDir . '/integrations/jetpack/jetpack.php', + 'PLL_Language' => $baseDir . '/include/language.php', + 'PLL_Language_Deprecated' => $baseDir . '/include/language-deprecated.php', + 'PLL_Language_Factory' => $baseDir . '/include/language-factory.php', + 'PLL_License' => $baseDir . '/include/license.php', + 'PLL_Links' => $baseDir . '/include/links.php', + 'PLL_Links_Abstract_Domain' => $baseDir . '/include/links-abstract-domain.php', + 'PLL_Links_Default' => $baseDir . '/include/links-default.php', + 'PLL_Links_Directory' => $baseDir . '/include/links-directory.php', + 'PLL_Links_Domain' => $baseDir . '/include/links-domain.php', + 'PLL_Links_Model' => $baseDir . '/include/links-model.php', + 'PLL_Links_Permalinks' => $baseDir . '/include/links-permalinks.php', + 'PLL_Links_Subdomain' => $baseDir . '/include/links-subdomain.php', + 'PLL_MO' => $baseDir . '/include/mo.php', + 'PLL_Model' => $baseDir . '/include/model.php', + 'PLL_Multilingual_Sitemaps_Provider' => $baseDir . '/modules/sitemaps/multilingual-sitemaps-provider.php', + 'PLL_Nav_Menu' => $baseDir . '/include/nav-menu.php', + 'PLL_No_Category_Base' => $baseDir . '/integrations/no-category-base/no-category-base.php', + 'PLL_OLT_Manager' => $baseDir . '/include/olt-manager.php', + 'PLL_Plugin_Updater' => $baseDir . '/install/plugin-updater.php', + 'PLL_Query' => $baseDir . '/include/query.php', + 'PLL_REST_Request' => $baseDir . '/include/rest-request.php', + 'PLL_Settings' => $baseDir . '/settings/settings.php', + 'PLL_Settings_Browser' => $baseDir . '/settings/settings-browser.php', + 'PLL_Settings_CPT' => $baseDir . '/settings/settings-cpt.php', + 'PLL_Settings_Licenses' => $baseDir . '/settings/settings-licenses.php', + 'PLL_Settings_Media' => $baseDir . '/settings/settings-media.php', + 'PLL_Settings_Module' => $baseDir . '/settings/settings-module.php', + 'PLL_Settings_Preview_Machine_Translation' => $baseDir . '/modules/machine-translation/settings-preview-machine-translation.php', + 'PLL_Settings_Preview_Share_Slug' => $baseDir . '/modules/share-slug/settings-preview-share-slug.php', + 'PLL_Settings_Preview_Translate_Slugs' => $baseDir . '/modules/translate-slugs/settings-preview-translate-slugs.php', + 'PLL_Settings_Sync' => $baseDir . '/modules/sync/settings-sync.php', + 'PLL_Settings_Url' => $baseDir . '/settings/settings-url.php', + 'PLL_Sitemaps' => $baseDir . '/modules/sitemaps/sitemaps.php', + 'PLL_Sitemaps_Domain' => $baseDir . '/modules/sitemaps/sitemaps-domain.php', + 'PLL_Static_Pages' => $baseDir . '/include/static-pages.php', + 'PLL_Switcher' => $baseDir . '/include/switcher.php', + 'PLL_Sync' => $baseDir . '/modules/sync/sync.php', + 'PLL_Sync_Metas' => $baseDir . '/modules/sync/sync-metas.php', + 'PLL_Sync_Post_Metas' => $baseDir . '/modules/sync/sync-post-metas.php', + 'PLL_Sync_Tax' => $baseDir . '/modules/sync/sync-tax.php', + 'PLL_Sync_Term_Metas' => $baseDir . '/modules/sync/sync-term-metas.php', + 'PLL_T15S' => $baseDir . '/install/t15s.php', + 'PLL_Table_Languages' => $baseDir . '/settings/table-languages.php', + 'PLL_Table_Settings' => $baseDir . '/settings/table-settings.php', + 'PLL_Table_String' => $baseDir . '/settings/table-string.php', + 'PLL_Term_Slug' => $baseDir . '/include/term-slug.php', + 'PLL_Translatable_Object' => $baseDir . '/include/translatable-object.php', + 'PLL_Translatable_Object_With_Types_Interface' => $baseDir . '/include/translatable-object-with-types-interface.php', + 'PLL_Translatable_Object_With_Types_Trait' => $baseDir . '/include/translatable-object-with-types-trait.php', + 'PLL_Translatable_Objects' => $baseDir . '/include/translatable-objects.php', + 'PLL_Translate_Option' => $baseDir . '/include/translate-option.php', + 'PLL_Translated_Object' => $baseDir . '/include/translated-object.php', + 'PLL_Translated_Post' => $baseDir . '/include/translated-post.php', + 'PLL_Translated_Term' => $baseDir . '/include/translated-term.php', + 'PLL_Twenty_Seventeen' => $baseDir . '/integrations/twenty-seventeen/twenty-seven-teen.php', + 'PLL_Upgrade' => $baseDir . '/install/upgrade.php', + 'PLL_WPML_API' => $baseDir . '/modules/wpml/wpml-api.php', + 'PLL_WPML_Compat' => $baseDir . '/modules/wpml/wpml-compat.php', + 'PLL_WPML_Config' => $baseDir . '/modules/wpml/wpml-config.php', + 'PLL_WPSEO' => $baseDir . '/integrations/wpseo/wpseo.php', + 'PLL_WPSEO_OGP' => $baseDir . '/integrations/wpseo/wpseo-ogp.php', + 'PLL_WP_Import' => $baseDir . '/integrations/wp-importer/wp-import.php', + 'PLL_WP_Sweep' => $baseDir . '/integrations/wp-sweep/wp-sweep.php', + 'PLL_Walker' => $baseDir . '/include/walker.php', + 'PLL_Walker_Dropdown' => $baseDir . '/include/walker-dropdown.php', + 'PLL_Walker_List' => $baseDir . '/include/walker-list.php', + 'PLL_Widget_Calendar' => $baseDir . '/include/widget-calendar.php', + 'PLL_Widget_Languages' => $baseDir . '/include/widget-languages.php', + 'PLL_Wizard' => $baseDir . '/modules/wizard/wizard.php', + 'PLL_WordPress_Importer' => $baseDir . '/integrations/wp-importer/wordpress-importer.php', + 'PLL_Yarpp' => $baseDir . '/integrations/yarpp/yarpp.php', + 'Polylang' => $baseDir . '/include/class-polylang.php', + 'WP_Syntex\\Polylang\\Model\\Languages' => $baseDir . '/include/Model/Languages.php', + 'WP_Syntex\\Polylang\\Model\\Post_Types' => $baseDir . '/include/Model/Post_Types.php', + 'WP_Syntex\\Polylang\\Model\\Taxonomies' => $baseDir . '/include/Model/Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Abstract_Option' => $baseDir . '/include/Options/Abstract_Option.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Abstract_Object_Types' => $baseDir . '/include/Options/Business/Abstract_Object_Types.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Browser' => $baseDir . '/include/Options/Business/Browser.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Default_Lang' => $baseDir . '/include/Options/Business/Default_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Domains' => $baseDir . '/include/Options/Business/Domains.php', + 'WP_Syntex\\Polylang\\Options\\Business\\First_Activation' => $baseDir . '/include/Options/Business/First_Activation.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Force_Lang' => $baseDir . '/include/Options/Business/Force_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Hide_Default' => $baseDir . '/include/Options/Business/Hide_Default.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Language_Taxonomies' => $baseDir . '/include/Options/Business/Language_Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Media_Support' => $baseDir . '/include/Options/Business/Media_Support.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Nav_Menus' => $baseDir . '/include/Options/Business/Nav_Menus.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Post_Types' => $baseDir . '/include/Options/Business/Post_Types.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Previous_Version' => $baseDir . '/include/Options/Business/Previous_Version.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Redirect_Lang' => $baseDir . '/include/Options/Business/Redirect_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Rewrite' => $baseDir . '/include/Options/Business/Rewrite.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Sync' => $baseDir . '/include/Options/Business/Sync.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Taxonomies' => $baseDir . '/include/Options/Business/Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Version' => $baseDir . '/include/Options/Business/Version.php', + 'WP_Syntex\\Polylang\\Options\\Options' => $baseDir . '/include/Options/Options.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_Boolean' => $baseDir . '/include/Options/Primitive/Abstract_Boolean.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_List' => $baseDir . '/include/Options/Primitive/Abstract_List.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_String' => $baseDir . '/include/Options/Primitive/Abstract_String.php', + 'WP_Syntex\\Polylang\\Options\\Registry' => $baseDir . '/include/Options/Registry.php', + 'WP_Syntex\\Polylang\\REST\\API' => $baseDir . '/modules/REST/API.php', + 'WP_Syntex\\Polylang\\REST\\Abstract_Controller' => $baseDir . '/modules/REST/Abstract_Controller.php', + 'WP_Syntex\\Polylang\\REST\\V1\\Languages' => $baseDir . '/modules/REST/V1/Languages.php', + 'WP_Syntex\\Polylang\\REST\\V1\\Settings' => $baseDir . '/modules/REST/V1/Settings.php', +); diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php b/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +register(true); + + return $loader; + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_static.php b/wp-content/plugins/polylang/vendor/composer/autoload_static.php new file mode 100644 index 0000000..da6054c --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_static.php @@ -0,0 +1,182 @@ + __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'PLL_AS3CF' => __DIR__ . '/../..' . '/integrations/wp-offload-media/as3cf.php', + 'PLL_Abstract_Sitemaps' => __DIR__ . '/../..' . '/modules/sitemaps/abstract-sitemaps.php', + 'PLL_Accept_Language' => __DIR__ . '/../..' . '/frontend/accept-language.php', + 'PLL_Accept_Languages_Collection' => __DIR__ . '/../..' . '/frontend/accept-languages-collection.php', + 'PLL_Admin' => __DIR__ . '/../..' . '/admin/admin.php', + 'PLL_Admin_Base' => __DIR__ . '/../..' . '/admin/admin-base.php', + 'PLL_Admin_Block_Editor' => __DIR__ . '/../..' . '/admin/admin-block-editor.php', + 'PLL_Admin_Classic_Editor' => __DIR__ . '/../..' . '/admin/admin-classic-editor.php', + 'PLL_Admin_Default_Term' => __DIR__ . '/../..' . '/admin/admin-default-term.php', + 'PLL_Admin_Filters' => __DIR__ . '/../..' . '/admin/admin-filters.php', + 'PLL_Admin_Filters_Columns' => __DIR__ . '/../..' . '/admin/admin-filters-columns.php', + 'PLL_Admin_Filters_Media' => __DIR__ . '/../..' . '/admin/admin-filters-media.php', + 'PLL_Admin_Filters_Post' => __DIR__ . '/../..' . '/admin/admin-filters-post.php', + 'PLL_Admin_Filters_Post_Base' => __DIR__ . '/../..' . '/admin/admin-filters-post-base.php', + 'PLL_Admin_Filters_Term' => __DIR__ . '/../..' . '/admin/admin-filters-term.php', + 'PLL_Admin_Filters_Widgets_Options' => __DIR__ . '/../..' . '/admin/admin-filters-widgets-options.php', + 'PLL_Admin_Links' => __DIR__ . '/../..' . '/admin/admin-links.php', + 'PLL_Admin_Model' => __DIR__ . '/../..' . '/admin/admin-model.php', + 'PLL_Admin_Nav_Menu' => __DIR__ . '/../..' . '/admin/admin-nav-menu.php', + 'PLL_Admin_Notices' => __DIR__ . '/../..' . '/admin/admin-notices.php', + 'PLL_Admin_Site_Health' => __DIR__ . '/../..' . '/modules/site-health/admin-site-health.php', + 'PLL_Admin_Static_Pages' => __DIR__ . '/../..' . '/admin/admin-static-pages.php', + 'PLL_Admin_Strings' => __DIR__ . '/../..' . '/admin/admin-strings.php', + 'PLL_Admin_Sync' => __DIR__ . '/../..' . '/modules/sync/admin-sync.php', + 'PLL_Aqua_Resizer' => __DIR__ . '/../..' . '/integrations/aqua-resizer/aqua-resizer.php', + 'PLL_Base' => __DIR__ . '/../..' . '/include/base.php', + 'PLL_CRUD_Posts' => __DIR__ . '/../..' . '/include/crud-posts.php', + 'PLL_CRUD_Terms' => __DIR__ . '/../..' . '/include/crud-terms.php', + 'PLL_Cache' => __DIR__ . '/../..' . '/include/cache.php', + 'PLL_Cache_Compat' => __DIR__ . '/../..' . '/integrations/cache/cache-compat.php', + 'PLL_Canonical' => __DIR__ . '/../..' . '/frontend/canonical.php', + 'PLL_Cft' => __DIR__ . '/../..' . '/integrations/custom-field-template/cft.php', + 'PLL_Choose_Lang' => __DIR__ . '/../..' . '/frontend/choose-lang.php', + 'PLL_Choose_Lang_Content' => __DIR__ . '/../..' . '/frontend/choose-lang-content.php', + 'PLL_Choose_Lang_Domain' => __DIR__ . '/../..' . '/frontend/choose-lang-domain.php', + 'PLL_Choose_Lang_Url' => __DIR__ . '/../..' . '/frontend/choose-lang-url.php', + 'PLL_Cookie' => __DIR__ . '/../..' . '/include/cookie.php', + 'PLL_Default_Term' => __DIR__ . '/../..' . '/include/default-term.php', + 'PLL_Domain_Mapping' => __DIR__ . '/../..' . '/integrations/domain-mapping/domain-mapping.php', + 'PLL_Duplicate_Post' => __DIR__ . '/../..' . '/integrations/duplicate-post/duplicate-post.php', + 'PLL_Featured_Content' => __DIR__ . '/../..' . '/integrations/jetpack/featured-content.php', + 'PLL_Filter_REST_Routes' => __DIR__ . '/../..' . '/include/filter-rest-routes.php', + 'PLL_Filters' => __DIR__ . '/../..' . '/include/filters.php', + 'PLL_Filters_Links' => __DIR__ . '/../..' . '/include/filters-links.php', + 'PLL_Filters_Sanitization' => __DIR__ . '/../..' . '/include/filters-sanitization.php', + 'PLL_Filters_Widgets_Options' => __DIR__ . '/../..' . '/include/filters-widgets-options.php', + 'PLL_Format_Util' => __DIR__ . '/../..' . '/include/format-util.php', + 'PLL_Frontend' => __DIR__ . '/../..' . '/frontend/frontend.php', + 'PLL_Frontend_Auto_Translate' => __DIR__ . '/../..' . '/frontend/frontend-auto-translate.php', + 'PLL_Frontend_Filters' => __DIR__ . '/../..' . '/frontend/frontend-filters.php', + 'PLL_Frontend_Filters_Links' => __DIR__ . '/../..' . '/frontend/frontend-filters-links.php', + 'PLL_Frontend_Filters_Search' => __DIR__ . '/../..' . '/frontend/frontend-filters-search.php', + 'PLL_Frontend_Filters_Widgets' => __DIR__ . '/../..' . '/frontend/frontend-filters-widgets.php', + 'PLL_Frontend_Links' => __DIR__ . '/../..' . '/frontend/frontend-links.php', + 'PLL_Frontend_Nav_Menu' => __DIR__ . '/../..' . '/frontend/frontend-nav-menu.php', + 'PLL_Frontend_Static_Pages' => __DIR__ . '/../..' . '/frontend/frontend-static-pages.php', + 'PLL_Install' => __DIR__ . '/../..' . '/install/install.php', + 'PLL_Install_Base' => __DIR__ . '/../..' . '/install/install-base.php', + 'PLL_Integrations' => __DIR__ . '/../..' . '/integrations/integrations.php', + 'PLL_Jetpack' => __DIR__ . '/../..' . '/integrations/jetpack/jetpack.php', + 'PLL_Language' => __DIR__ . '/../..' . '/include/language.php', + 'PLL_Language_Deprecated' => __DIR__ . '/../..' . '/include/language-deprecated.php', + 'PLL_Language_Factory' => __DIR__ . '/../..' . '/include/language-factory.php', + 'PLL_License' => __DIR__ . '/../..' . '/include/license.php', + 'PLL_Links' => __DIR__ . '/../..' . '/include/links.php', + 'PLL_Links_Abstract_Domain' => __DIR__ . '/../..' . '/include/links-abstract-domain.php', + 'PLL_Links_Default' => __DIR__ . '/../..' . '/include/links-default.php', + 'PLL_Links_Directory' => __DIR__ . '/../..' . '/include/links-directory.php', + 'PLL_Links_Domain' => __DIR__ . '/../..' . '/include/links-domain.php', + 'PLL_Links_Model' => __DIR__ . '/../..' . '/include/links-model.php', + 'PLL_Links_Permalinks' => __DIR__ . '/../..' . '/include/links-permalinks.php', + 'PLL_Links_Subdomain' => __DIR__ . '/../..' . '/include/links-subdomain.php', + 'PLL_MO' => __DIR__ . '/../..' . '/include/mo.php', + 'PLL_Model' => __DIR__ . '/../..' . '/include/model.php', + 'PLL_Multilingual_Sitemaps_Provider' => __DIR__ . '/../..' . '/modules/sitemaps/multilingual-sitemaps-provider.php', + 'PLL_Nav_Menu' => __DIR__ . '/../..' . '/include/nav-menu.php', + 'PLL_No_Category_Base' => __DIR__ . '/../..' . '/integrations/no-category-base/no-category-base.php', + 'PLL_OLT_Manager' => __DIR__ . '/../..' . '/include/olt-manager.php', + 'PLL_Plugin_Updater' => __DIR__ . '/../..' . '/install/plugin-updater.php', + 'PLL_Query' => __DIR__ . '/../..' . '/include/query.php', + 'PLL_REST_Request' => __DIR__ . '/../..' . '/include/rest-request.php', + 'PLL_Settings' => __DIR__ . '/../..' . '/settings/settings.php', + 'PLL_Settings_Browser' => __DIR__ . '/../..' . '/settings/settings-browser.php', + 'PLL_Settings_CPT' => __DIR__ . '/../..' . '/settings/settings-cpt.php', + 'PLL_Settings_Licenses' => __DIR__ . '/../..' . '/settings/settings-licenses.php', + 'PLL_Settings_Media' => __DIR__ . '/../..' . '/settings/settings-media.php', + 'PLL_Settings_Module' => __DIR__ . '/../..' . '/settings/settings-module.php', + 'PLL_Settings_Preview_Machine_Translation' => __DIR__ . '/../..' . '/modules/machine-translation/settings-preview-machine-translation.php', + 'PLL_Settings_Preview_Share_Slug' => __DIR__ . '/../..' . '/modules/share-slug/settings-preview-share-slug.php', + 'PLL_Settings_Preview_Translate_Slugs' => __DIR__ . '/../..' . '/modules/translate-slugs/settings-preview-translate-slugs.php', + 'PLL_Settings_Sync' => __DIR__ . '/../..' . '/modules/sync/settings-sync.php', + 'PLL_Settings_Url' => __DIR__ . '/../..' . '/settings/settings-url.php', + 'PLL_Sitemaps' => __DIR__ . '/../..' . '/modules/sitemaps/sitemaps.php', + 'PLL_Sitemaps_Domain' => __DIR__ . '/../..' . '/modules/sitemaps/sitemaps-domain.php', + 'PLL_Static_Pages' => __DIR__ . '/../..' . '/include/static-pages.php', + 'PLL_Switcher' => __DIR__ . '/../..' . '/include/switcher.php', + 'PLL_Sync' => __DIR__ . '/../..' . '/modules/sync/sync.php', + 'PLL_Sync_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-metas.php', + 'PLL_Sync_Post_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-post-metas.php', + 'PLL_Sync_Tax' => __DIR__ . '/../..' . '/modules/sync/sync-tax.php', + 'PLL_Sync_Term_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-term-metas.php', + 'PLL_T15S' => __DIR__ . '/../..' . '/install/t15s.php', + 'PLL_Table_Languages' => __DIR__ . '/../..' . '/settings/table-languages.php', + 'PLL_Table_Settings' => __DIR__ . '/../..' . '/settings/table-settings.php', + 'PLL_Table_String' => __DIR__ . '/../..' . '/settings/table-string.php', + 'PLL_Term_Slug' => __DIR__ . '/../..' . '/include/term-slug.php', + 'PLL_Translatable_Object' => __DIR__ . '/../..' . '/include/translatable-object.php', + 'PLL_Translatable_Object_With_Types_Interface' => __DIR__ . '/../..' . '/include/translatable-object-with-types-interface.php', + 'PLL_Translatable_Object_With_Types_Trait' => __DIR__ . '/../..' . '/include/translatable-object-with-types-trait.php', + 'PLL_Translatable_Objects' => __DIR__ . '/../..' . '/include/translatable-objects.php', + 'PLL_Translate_Option' => __DIR__ . '/../..' . '/include/translate-option.php', + 'PLL_Translated_Object' => __DIR__ . '/../..' . '/include/translated-object.php', + 'PLL_Translated_Post' => __DIR__ . '/../..' . '/include/translated-post.php', + 'PLL_Translated_Term' => __DIR__ . '/../..' . '/include/translated-term.php', + 'PLL_Twenty_Seventeen' => __DIR__ . '/../..' . '/integrations/twenty-seventeen/twenty-seven-teen.php', + 'PLL_Upgrade' => __DIR__ . '/../..' . '/install/upgrade.php', + 'PLL_WPML_API' => __DIR__ . '/../..' . '/modules/wpml/wpml-api.php', + 'PLL_WPML_Compat' => __DIR__ . '/../..' . '/modules/wpml/wpml-compat.php', + 'PLL_WPML_Config' => __DIR__ . '/../..' . '/modules/wpml/wpml-config.php', + 'PLL_WPSEO' => __DIR__ . '/../..' . '/integrations/wpseo/wpseo.php', + 'PLL_WPSEO_OGP' => __DIR__ . '/../..' . '/integrations/wpseo/wpseo-ogp.php', + 'PLL_WP_Import' => __DIR__ . '/../..' . '/integrations/wp-importer/wp-import.php', + 'PLL_WP_Sweep' => __DIR__ . '/../..' . '/integrations/wp-sweep/wp-sweep.php', + 'PLL_Walker' => __DIR__ . '/../..' . '/include/walker.php', + 'PLL_Walker_Dropdown' => __DIR__ . '/../..' . '/include/walker-dropdown.php', + 'PLL_Walker_List' => __DIR__ . '/../..' . '/include/walker-list.php', + 'PLL_Widget_Calendar' => __DIR__ . '/../..' . '/include/widget-calendar.php', + 'PLL_Widget_Languages' => __DIR__ . '/../..' . '/include/widget-languages.php', + 'PLL_Wizard' => __DIR__ . '/../..' . '/modules/wizard/wizard.php', + 'PLL_WordPress_Importer' => __DIR__ . '/../..' . '/integrations/wp-importer/wordpress-importer.php', + 'PLL_Yarpp' => __DIR__ . '/../..' . '/integrations/yarpp/yarpp.php', + 'Polylang' => __DIR__ . '/../..' . '/include/class-polylang.php', + 'WP_Syntex\\Polylang\\Model\\Languages' => __DIR__ . '/../..' . '/include/Model/Languages.php', + 'WP_Syntex\\Polylang\\Model\\Post_Types' => __DIR__ . '/../..' . '/include/Model/Post_Types.php', + 'WP_Syntex\\Polylang\\Model\\Taxonomies' => __DIR__ . '/../..' . '/include/Model/Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Abstract_Option' => __DIR__ . '/../..' . '/include/Options/Abstract_Option.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Abstract_Object_Types' => __DIR__ . '/../..' . '/include/Options/Business/Abstract_Object_Types.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Browser' => __DIR__ . '/../..' . '/include/Options/Business/Browser.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Default_Lang' => __DIR__ . '/../..' . '/include/Options/Business/Default_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Domains' => __DIR__ . '/../..' . '/include/Options/Business/Domains.php', + 'WP_Syntex\\Polylang\\Options\\Business\\First_Activation' => __DIR__ . '/../..' . '/include/Options/Business/First_Activation.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Force_Lang' => __DIR__ . '/../..' . '/include/Options/Business/Force_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Hide_Default' => __DIR__ . '/../..' . '/include/Options/Business/Hide_Default.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Language_Taxonomies' => __DIR__ . '/../..' . '/include/Options/Business/Language_Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Media_Support' => __DIR__ . '/../..' . '/include/Options/Business/Media_Support.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Nav_Menus' => __DIR__ . '/../..' . '/include/Options/Business/Nav_Menus.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Post_Types' => __DIR__ . '/../..' . '/include/Options/Business/Post_Types.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Previous_Version' => __DIR__ . '/../..' . '/include/Options/Business/Previous_Version.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Redirect_Lang' => __DIR__ . '/../..' . '/include/Options/Business/Redirect_Lang.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Rewrite' => __DIR__ . '/../..' . '/include/Options/Business/Rewrite.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Sync' => __DIR__ . '/../..' . '/include/Options/Business/Sync.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Taxonomies' => __DIR__ . '/../..' . '/include/Options/Business/Taxonomies.php', + 'WP_Syntex\\Polylang\\Options\\Business\\Version' => __DIR__ . '/../..' . '/include/Options/Business/Version.php', + 'WP_Syntex\\Polylang\\Options\\Options' => __DIR__ . '/../..' . '/include/Options/Options.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_Boolean' => __DIR__ . '/../..' . '/include/Options/Primitive/Abstract_Boolean.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_List' => __DIR__ . '/../..' . '/include/Options/Primitive/Abstract_List.php', + 'WP_Syntex\\Polylang\\Options\\Primitive\\Abstract_String' => __DIR__ . '/../..' . '/include/Options/Primitive/Abstract_String.php', + 'WP_Syntex\\Polylang\\Options\\Registry' => __DIR__ . '/../..' . '/include/Options/Registry.php', + 'WP_Syntex\\Polylang\\REST\\API' => __DIR__ . '/../..' . '/modules/REST/API.php', + 'WP_Syntex\\Polylang\\REST\\Abstract_Controller' => __DIR__ . '/../..' . '/modules/REST/Abstract_Controller.php', + 'WP_Syntex\\Polylang\\REST\\V1\\Languages' => __DIR__ . '/../..' . '/modules/REST/V1/Languages.php', + 'WP_Syntex\\Polylang\\REST\\V1\\Settings' => __DIR__ . '/../..' . '/modules/REST/V1/Settings.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInit8f862f0d8b75b7170c1f5eb4256b99b4::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/installed.json b/wp-content/plugins/polylang/vendor/composer/installed.json new file mode 100644 index 0000000..f20a6c4 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": false, + "dev-package-names": [] +} diff --git a/wp-content/plugins/polylang/vendor/composer/installed.php b/wp-content/plugins/polylang/vendor/composer/installed.php new file mode 100644 index 0000000..7d945ce --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/installed.php @@ -0,0 +1,23 @@ + array( + 'name' => 'wpsyntex/polylang', + 'pretty_version' => '3.7.x-dev', + 'version' => '3.7.9999999.9999999-dev', + 'reference' => 'da289eb27d7dcf672fead919e9c2ba87246910a5', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'wpsyntex/polylang' => array( + 'pretty_version' => '3.7.x-dev', + 'version' => '3.7.9999999.9999999-dev', + 'reference' => 'da289eb27d7dcf672fead919e9c2ba87246910a5', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/wp-content/plugins/polylang/vendor/composer/platform_check.php b/wp-content/plugins/polylang/vendor/composer/platform_check.php new file mode 100644 index 0000000..589e9e7 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/wp-content/uploads/wpcf7_uploads/.htaccess b/wp-content/uploads/wpcf7_uploads/.htaccess new file mode 100644 index 0000000..55c49d6 --- /dev/null +++ b/wp-content/uploads/wpcf7_uploads/.htaccess @@ -0,0 +1,9 @@ +# Apache 2.4+ + + Require all denied + + +# Apache 2.2 + + Deny from all +