commit 72f14e83cd82528bdcf6f2111a31f82ce829ec0c Author: ORG-wiki Date: Sat Dec 4 12:05:10 2021 +1300 (Grav GitSync) Automatic Commit from ORG-wiki diff --git a/pages/01.blog/blog.de.md b/pages/01.blog/blog.de.md new file mode 100755 index 0000000..0e9aafa --- /dev/null +++ b/pages/01.blog/blog.de.md @@ -0,0 +1,20 @@ +--- +title: News +blog_url: blog +body_classes: 'header-image fullwidth' +sitemap: + changefreq: monthly + priority: 1.03 +content: + items: '@self.children' + order: + by: date + dir: desc + limit: 5 + pagination: true +feed: + description: 'Sample Blog Description' + limit: 10 +pagination: true +--- + diff --git a/pages/01.blog/blog.en.md b/pages/01.blog/blog.en.md new file mode 100644 index 0000000..0e9aafa --- /dev/null +++ b/pages/01.blog/blog.en.md @@ -0,0 +1,20 @@ +--- +title: News +blog_url: blog +body_classes: 'header-image fullwidth' +sitemap: + changefreq: monthly + priority: 1.03 +content: + items: '@self.children' + order: + by: date + dir: desc + limit: 5 + pagination: true +feed: + description: 'Sample Blog Description' + limit: 10 +pagination: true +--- + diff --git a/pages/01.blog/our-news-blog-is-online-with-grav/item.de.md b/pages/01.blog/our-news-blog-is-online-with-grav/item.de.md new file mode 100644 index 0000000..d41434f --- /dev/null +++ b/pages/01.blog/our-news-blog-is-online-with-grav/item.de.md @@ -0,0 +1,86 @@ +--- +title: 'Unser News/Blog ist online mit GRAV' +taxonomy: + category: + - news + tag: + - 'TECH SAVIOURS' +media_order: 'ts&grav.png' +aura: + author: dan +date: '01-12-2021 09:37' +published: false +--- + +Wir freuen uns sehr, unseren neuen News-Bereich zu veröffentlichen! + +# WordPress, Drupal, Grav ... welches?! +Nachdem wir einige Optionen wie WordPress, Drupal & Co. verglichen haben, haben wir uns für GRAV entschieden. Das ist ein großartiges CMS (Content Management System), das in php geschrieben ist. Eigentlich genau wie die anderen beiden genannten, aber GRAV braucht keine Datenbank. Es speichert Daten in einer Flat-File CMS, die in Ordnern organisiert sind, anstatt in einer Datenbank. Das macht es wirklich schnell und sehr leicht. +Die erste Version von GRAV wurde am 30. Juli 2014 veröffentlicht, es ist also relativ neu im Vergleich zu WordPress (2003) und Drupal (2001), aber es hat sich zu einer bekannten CMS-Alternative entwickelt. GRAV wird von weniger als 0,1% aller CMS-Systeme verwendet - [w3techs.com](https://w3techs.com/technologies/details/cm-grav). Das ist nicht viel im Vergleich zu den Großen - WordPress hat den Markt mit respektablen [65,1%](https://w3techs.com/technologies/details/cm-wordpress) im Griff und Drupals Marktanteil beträgt [2,1%](https://w3techs.com/technologies/details/cm-drupal). +Wirf einen Blick auf die Nutzungsstatistiken der beliebtesten [Content Management Systeme](https://w3techs.com/technologies/overview/content_management). + +# Drupal oder GRAV +Unser Hauptvergleich am Ende waren Drupal und GRAV. Beide haben ausgezeichnete Sicherheitspraktiken. +Bei der Überprüfung der neuesten Schwachstellenstatistiken auf CVE-Details für [Drupal](https://www.cvedetails.com/product/2387/Drupal-Drupal.html?vendor_id=1367) und [GRAV](https://www.cvedetails.com/product/59205/Getgrav-Grav-Cms.html?vendor_id=20511) haben wir uns mehr in Richtung GRAV bewegt. +Und am Ende sind unsere Newsfeeds nur ein paar Buchstaben und Bilder. Die anderen wären viel zu umfangreich für unser kleines Ziel. +GRAV verwendet [Markdown-Syntax](https://learn.getgrav.org/17/content/markdown), die wir bevorzugen, und die Konfigurationsdateien/Seiten sind in [YAML-Syntax](https://learn.getgrav.org/17/advanced/yaml), die man in vielen Tools wie Ansible, Kubernetes, Prometheus, Matrix ... findet, um nur einige zu nennen. +GRAV passt also nicht nur zu unseren Bedürfnissen, sondern auch zu unserer Infrastruktur. + +# Pinpress theme +Nachdem die Entscheidung gefallen war, begannen wir mit der Suche nach einem Theme. Und davon gibt es eine ganze Menge. Einfach und leicht zu lesen war nun das nächste Ziel. Und ein ähnliches Aussehen wie unsere Haupt-Website, die mit [Bootstrap](https://getbootstrap.com/) erstellt wurde. +Das [PinPress-Theme](https://demo.getgrav.org/pinpress-skeleton/) scheint eine gute Wahl zu sein, und bisher haben wir nicht wirklich etwas zu beanstanden. Es war ein bisschen schwierig, es an unser Design anzupassen, weil die Standardeinstellungen des Themes nicht so gut funktionierten, wie wir dachten. + +## Theme inheritance aka child theme +Wir begannen zunächst mit einem [_Inheriting theme_](https://learn.getgrav.org/16/themes/customization#inheriting-manually) (Child-Theme), mit dem wir unsere Änderungen vornehmen können und die Möglichkeit behalten, das Theme zu aktualisieren, ohne unsere Änderungen zu überschreiben. +Sobald wir alles fertig hatten, war unser erster Schritt "Aufräumen". Wir haben alle Verbindungen zu Drittanbietern entfernt, um noch mehr Leistung aus GRAV herauszuholen und eine bessere Benutzerfreundlichkeit beim Lesen unserer Nachrichten und vor allem beim Durchklicken des Inhalts zu erreichen, aber auch für uns, während wir daran arbeiten. +Um alles direkt vom Server aus zu hosten, haben wir uns außerdem entschlossen, das Icon Pack von [Fork Awesome](https://forkaweso.me/Fork-Awesome/) herunterzuladen. Ja, sie sind wirklich ... awesome! +Schließlich haben wir dem Theme unsere persönliche Note gegeben, wie Favicon, Logo, Footer etc. und die [twig-Dateien](https://en.wikipedia.org/wiki/Twig_(template_engine)) an unsere Bedürfnisse angepasst. + +# MIT license +GRAV ist lizenziert unter der [MIT-Lizenz](https://github.com/getgrav/grav/blob/develop/LICENSE.txt). Dies ist eine sehr unkomplizierte Lizenz. + +>The MIT License (MIT) +> +>Copyright (c) 2021 Grav +> +>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. + +## Rechte +Du hast das Recht, Kopien der Software zu verwenden, zu vervielfältigen, zu modifizieren, zusammenzuführen, zu verbreiten, zu veröffentlichen, Unterlizenzen zu vergeben und zu verkaufen. + +## Bedingungen +Der MIT-Lizenztext und der Copyright-Hinweis müssen weiterhin in jedem unveränderten MIT-lizenzierten Code enthalten sein. Dein neuer Code kann unter einer anderen Lizenz stehen. + +# Fazit +Das Ergebnis ist nun sichtbar und wir sind sehr zufrieden damit. +Aber nicht alles ist perfekt. Es gibt ein großes Problem, das GRAV hat, insbesondere für Nachrichten- und Blog-Autoren. Das automatische Posten von Nachrichten auf verschiedenen Medienkanälen ist auch 2021 noch mühsam. Wahrscheinlich sogar im Jahr 2022, das nicht mehr weit entfernt ist. Du musst deine Nachrichten manuell posten oder andere Lösungen wie [IFTTT](https://ifttt.com/explore/new_to_ifttt) finden. +Unser Plan ist es, Nachrichten alle zwei Wochen oder wöchentlich zu veröffentlichen. Für uns ist es in Ordnung, diese manuell in den sozialen Medien zu posten, bis wir eine andere Lösung finden, die zu uns passt. +Wir werden also sehen, was die Zukunft bringt und wie GRAV auf lange Sicht funktioniert. + +Wenn du mehr über GRAV lesen möchtest, schau dir den Link an - [Was ist Grav?](https://learn.getgrav.org/17/basics/what-is-grav) + +# Und was jetzt? + +Wie wir im [Fazit](#fazit) erwähnt haben, ist unser Plan, alle zwei Wochen oder wöchentlich News zu veröffentlichen. +Damit du unsere News nach Belieben verfolgen kannst, haben wir einige Accounts in den sozialen Medien eingerichtet. Du findest die Links in der grauen Box, oben rechts oder unter den News, je nachdem, wie dein Gerät/deine Auflösung eingestellt ist. + +@reddit followers +Nicht jeder Artikel wird in unserem Subreddit veröffentlicht. Da die Regeln nicht dazu gedacht sind, für sich selbst zu werben, werden wir nur Informationen über den Datenschutz und andere Dinge posten, ohne unsere Nachrichten/Blogs zu erwaehnen. Das bedeutet, dass alle Änderungen an TECH SAVIOURS nur auf den anderen Kanälen erscheinen werden. + +Wenn du irgendwelche Vorschläge oder Feedback hast, lass es uns wissen. diff --git a/pages/01.blog/our-news-blog-is-online-with-grav/item.en.md b/pages/01.blog/our-news-blog-is-online-with-grav/item.en.md new file mode 100644 index 0000000..758c6b7 --- /dev/null +++ b/pages/01.blog/our-news-blog-is-online-with-grav/item.en.md @@ -0,0 +1,86 @@ +--- +title: 'Our News/Blog is online with GRAV' +taxonomy: + category: + - news + tag: + - 'TECH SAVIOURS' +media_order: 'ts&grav.png' +aura: + author: dan +date: '01-12-2021 09:37' +published: false +--- + +We are very excited to launch our new News section! + +# WordPress, Drupal, Grav ... which one?! +After comparing a few options out there like WordPress, Drupal & co. we've decided to go with GRAV. Which is a great CMS (Content Management System) written in php. Actually just like the other two mentioned, but GRAV doesn't need a database. It stores data in a type of flat-file CMS that are organised in folders rather than in a database. Which makes it really quick and very lightweight. +GRAV's first release was in July 30, 2014, so it's relatively new compared to WordPress (2003) and Drupal (2001) for example, but it has become a well-known CMS alternative. GRAV is used by less than 0.1% of all CMS systems - [w3techs.com](https://w3techs.com/technologies/details/cm-grav). It's not much compare to the big one - WordPress has the market under control with a respectable [65.1%](https://w3techs.com/technologies/details/cm-wordpress) and Drupal's market share is [2.1%](https://w3techs.com/technologies/details/cm-drupal). +Take a look at the usage statistics of the most popular [Content Management Systems](https://w3techs.com/technologies/overview/content_management). + +# Drupal or GRAV +Our main comparison at the end where Drupal and GRAV. Both have excellent security practices. +Checking the latest vulnerability statistics on CVE-Details for [Drupal](https://www.cvedetails.com/product/2387/Drupal-Drupal.html?vendor_id=1367) and [GRAV](https://www.cvedetails.com/product/59205/Getgrav-Grav-Cms.html?vendor_id=20511), we moved more into the direction of GRAV. +And in the end, our news feeds are just a few letters and pictures. The others would be way too heavy for our little goal. +GRAV uses [Markdown syntax](https://learn.getgrav.org/17/content/markdown), which we prefer, and the configuration files/pages are in [YAML syntax](https://learn.getgrav.org/17/advanced/yaml), which you will find in many tools like Ansible, Kubernetes, Prometheus, Matrix ... just to name a few. +So GRAV not only fits our needs, it also matches our infrastructure. + +# Pinpress theme +After the decision was made, we started looking for a theme. And there are quite a few of them. Simple and easy to read was now the next goal. And a similar look to our main website, which is made via [Bootstrap](https://getbootstrap.com/). +The [PinPress theme](https://demo.getgrav.org/pinpress-skeleton/) seems to be a good choice and so far we don't really have anything to complain about. It was a bit difficult to adapt it to our design because the default theme settings didn't work as good as we thought. + +## Theme inheritance aka child theme +We started first with an [_Inheriting theme_](https://learn.getgrav.org/16/themes/customization#inheriting-manually) (child theme), with that we can make our changes and keep the possibility to update the theme without overwriting our changes. +Once we had everything ready, our first step was a "clean up". We've removed all third-party connections to get even more performance out of GRAV to achieve a better usability when reading our news and especially clicking through the content, but also for us while we work on it. +In order to host everything directly from the server, we also decided to download [Fork Awesome's](https://forkaweso.me/Fork-Awesome/) icon pack. Yeah, they are really ... awesome! +Finally, we gave the theme our personal touch like favicon, logo, footer etc. and changed the [twig files](https://en.wikipedia.org/wiki/Twig_(template_engine)) to our needs. + +# MIT license +GRAV is licensed under the [MIT License](https://github.com/getgrav/grav/blob/develop/LICENSE.txt). This is a very straightforward license. + +>The MIT License (MIT) +> +>Copyright (c) 2021 Grav +> +>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. + +## Rights +You have the right to use, copy, modify, merge, distribute, publish, sublicense, and sell copies of the software. + +## Conditions +MIT License text & copyright notice must still be included with any unmodified MIT-licensed code. Your new work can be another license. + +# Conclusion +The outcome is now visible and we are very happy with it. +But not everything is perfect. There is one big problem GRAV has, especially for news and blog writers. Posting news automatically to various media channels will still be a hassle in 2021. Probably even in 2022, which is not far away. You need to post your messages manually or find other solutions like [IFTTT](https://ifttt.com/explore/new_to_ifttt). +Our plan is to publish news every two weeks or weekly. It's fine for us to manually post this on social media until we find another solution that suits us. +So we will see what the future will bring and how GRAV works in the long run. + +If you want to read more about GRAV check the link - [What is Grav?](https://learn.getgrav.org/17/basics/what-is-grav) + +# What next? + +As we mentioned in [conclusion](#conclusion) our plan is to publish news every two weeks or weekly. +To allow you to follow our news as you like, we have set up a few more social media accounts. You can find the links in the gray box at the top right or below the news, depending on your device/resolution settings. + +@reddit followers +Not every article will be published in our subreddit. Reddit rules are not meant to promote yourself, so we will only post information about privacy and other things without promoting our news/blog. This means that any changes to TECH SAVIOURS will only appear on the other channels. + +If you have any suggestions or feedback, let us know. diff --git a/pages/01.blog/our-news-blog-is-online-with-grav/ts&grav.png b/pages/01.blog/our-news-blog-is-online-with-grav/ts&grav.png new file mode 100755 index 0000000..4635225 Binary files /dev/null and b/pages/01.blog/our-news-blog-is-online-with-grav/ts&grav.png differ diff --git a/pages/01.blog/tech-saviours-org-nz-online-de-why/item.de.md b/pages/01.blog/tech-saviours-org-nz-online-de-why/item.de.md new file mode 100644 index 0000000..22730d3 --- /dev/null +++ b/pages/01.blog/tech-saviours-org-nz-online-de-why/item.de.md @@ -0,0 +1,38 @@ +--- +title: 'TECH SAVIOURS .ORG, .NZ, .ONLINE, .DE ... Warum?' +taxonomy: + category: + - news + tag: + - 'TECH SAVIOURS' +aura: + author: dan +media_order: techsaviours-hierarchy.png +published: false +--- + +# Hierarchie von TECH SAVIOURS + +1. [techsaviours.org](https://techsaviours.org) +2. [techsaviours.online](https://techsaviours.online) +3. [techsaviours.nz](https://techsaviours.nz) +4. [tech-saviours.de](https://tech-saviours.de) + +Nun, eigentlich ist nur .ORG so etwas wie an erster Stelle. Du kannst wählen, was für dich wichtig ist. +Das kann für manche verwirrend sein, aber es gibt einen triftigen Grund für all dies. Wenn wir uns nur für eine Domäne entscheiden müssten, welche wäre es dann? .ORG oder .ONLINE? Wir haben es unten beschrieben. + +# Community - _.ORG_ +"Sharing is caring" ist eine art Redewendung, die sehr wichtig ist. Ohne die gemeinsame Nutzung von Wissen und Diensten wäre es nicht wirklich möglich, ein angemessenes Datenschutzziel zu erreichen. Um eine freie Welt für alle zu erreichen, braucht es offene Codes, Vertrauen und mehr Auswahlmöglichkeiten. +Deshalb wollen wir dir die Möglichkeit geben, deinen eigenen Server einzurichten und andere Online-Dienste zu nutzen, die nicht Facebook, Google, Apple, Microsoft, ... heißen. +Es gibt andere Dienste/Software/Unternehmen, die deine persönlichen Daten nicht sammeln, und wir können dir einen Weg zeigen, von dem du vielleicht nicht einmal wusstest, dass er existiert. +TECH **SAVIOURS** _.ORG_ ist ein weiterer Teil der vielen verschiedenen Datenschutz-Communities da draußen. Deine Möglichkeiten werden also immer größer und größer. + +# Lokale Unterstützung - _.NZ_ & _.DE_ +Wir wollten spezifische Domains für unseren lokalen Support, der sich in Neuseeland und Deutschland befindet. Für eine angemessene Vermarktung/Werbung wollten wir vertrauenswürdige Domains für die einzelnen Länder haben - .DE & .NZ. +Alles unter einer Domain zu haben, kann zu Konflikten führen. Das gilt nicht nur für die Sprache. Deshalb halten wir alles getrennt, um unsere lokalen Informationen nur für die spezifischen Länder/Domains zu nutzen. + +# Rest of the world - _.ONLINE_ +Wenn du in einem Land lebst, das nicht unter [Lokale Unterstützung](#lokale-unterstuetzung-nz-de) aufgeführt ist, und nicht die [Zeit oder das Wissen](#community-org) hast, um deinen eigenen Server zu erstellen, können wir dir dabei helfen, aber nur online. +Ein weiterer Grund ist, dass die Werbung in anderen Ländern mit .DE oder .NZ unseriös und verwirrend ist. + +Ich hoffe, dies hilft ein wenig zu verstehen, wie TECH **SAVIOURS** aufgebaut ist. Wenn du noch Fragen hast, schick uns einfach eine E-Mail. \ No newline at end of file diff --git a/pages/01.blog/tech-saviours-org-nz-online-de-why/item.en.md b/pages/01.blog/tech-saviours-org-nz-online-de-why/item.en.md new file mode 100644 index 0000000..008b8da --- /dev/null +++ b/pages/01.blog/tech-saviours-org-nz-online-de-why/item.en.md @@ -0,0 +1,38 @@ +--- +title: 'TECH SAVIOURS .ORG, .NZ, .ONLINE, .DE ... Why?' +taxonomy: + category: + - news + tag: + - 'TECH SAVIOURS' +aura: + author: dan +media_order: techsaviours-hierarchy.png +published: false +--- + +# Hierarchy of TECH SAVIOURS + +1. [techsaviours.org](https://techsaviours.org) +2. [techsaviours.online](https://techsaviours.online) +3. [techsaviours.nz](https://techsaviours.nz) +4. [tech-saviours.de](https://tech-saviours.de) + +Well, actually only .ORG is something like at the first place. You can choose whatever is important for you. +This can be confusing for some, but there is a valid reason for all of this. If we would choose only one domain, which one would it be? .ORG or .ONLINE? We described it below. + +# Community - _.ORG_ +Sharing is caring is a phrase that is very important. Without sharing of knowledge and services, it would not really be possible to achieve an adequate data privacy goal. Reaching an open world for everyone, it needs open codes, trust and more options to choose from. +So we want to give you the opportunity to set up your own server and use other online services not called Facebook, Google, Apple, Microsoft, ... . +There are other services/software/companies that won't collect your personal data and we can show you a way that you may not even have known that it exists. +TECH **SAVIOURS** _.ORG_ is another part of many different privacy communities out there. So your options are getting just bigger and bigger. + +# Local Support - _.NZ_ & _.DE_ +We wanted to have specific domains for our local support, which are located in New Zealand and Germany. For proper marketing/advertising we wanted to have trusted domains for the specific countries - .DE & .NZ. +Having everything under one domain can create conflicts. Which is not only the language. So we keep everything separate to keep our local information for the specific countries/domain only. + +# Rest of the world - _.ONLINE_ +If you live in a country not listed under [local support](#local-support-nz-de) and don't have the [time or knowledge](#community-org) to create your own server, we can help you with that, but only online. +Another reason is that advertising in other countries with .DE or .NZ is untrustworthy and confusing. + +I hope this helps a little to understand how TECH **SAVIOURS** is structured. If you still have questions, just send us an email. \ No newline at end of file diff --git a/pages/01.blog/tech-saviours-org-nz-online-de-why/techsaviours-hierarchy.png b/pages/01.blog/tech-saviours-org-nz-online-de-why/techsaviours-hierarchy.png new file mode 100755 index 0000000..ac949ca Binary files /dev/null and b/pages/01.blog/tech-saviours-org-nz-online-de-why/techsaviours-hierarchy.png differ diff --git a/pages/02.home/external.de.md b/pages/02.home/external.de.md new file mode 100644 index 0000000..08dc03b --- /dev/null +++ b/pages/02.home/external.de.md @@ -0,0 +1,5 @@ +--- +title: Home +external_url: 'https://techsaviours.org/index-de.html' +--- + diff --git a/pages/02.home/external.en.md b/pages/02.home/external.en.md new file mode 100644 index 0000000..be614d4 --- /dev/null +++ b/pages/02.home/external.en.md @@ -0,0 +1,5 @@ +--- +title: Home +external_url: 'https://techsaviours.org/' +--- + diff --git a/pages/03.wiki/external.de.md b/pages/03.wiki/external.de.md new file mode 100644 index 0000000..d6f28cc --- /dev/null +++ b/pages/03.wiki/external.de.md @@ -0,0 +1,5 @@ +--- +title: Wiki +external_url: 'https://wiki.techsaviours.org/de' +--- + diff --git a/pages/03.wiki/external.en.md b/pages/03.wiki/external.en.md new file mode 100644 index 0000000..9505bed --- /dev/null +++ b/pages/03.wiki/external.en.md @@ -0,0 +1,5 @@ +--- +title: Wiki +external_url: 'https://wiki.techsaviours.org' +--- + diff --git a/pages/04.free-services/external.de.md b/pages/04.free-services/external.de.md new file mode 100644 index 0000000..6f57642 --- /dev/null +++ b/pages/04.free-services/external.de.md @@ -0,0 +1,5 @@ +--- +title: 'Warum?' +external_url: 'https://techsaviours.org/index-de.html#services' +--- + diff --git a/pages/04.free-services/external.en.md b/pages/04.free-services/external.en.md new file mode 100644 index 0000000..71f8f06 --- /dev/null +++ b/pages/04.free-services/external.en.md @@ -0,0 +1,5 @@ +--- +title: 'Why?' +external_url: 'https://techsaviours.org/#services' +--- + diff --git a/pages/05.tutorials/external.de.md b/pages/05.tutorials/external.de.md new file mode 100644 index 0000000..1f891eb --- /dev/null +++ b/pages/05.tutorials/external.de.md @@ -0,0 +1,5 @@ +--- +title: Tutorials +external_url: 'https://techsaviours.org/tutorials-de.html' +--- + diff --git a/pages/05.tutorials/external.en.md b/pages/05.tutorials/external.en.md new file mode 100644 index 0000000..1af1bd5 --- /dev/null +++ b/pages/05.tutorials/external.en.md @@ -0,0 +1,5 @@ +--- +title: Tutorials +external_url: 'https://techsaviours.org/tutorials.html' +--- + diff --git a/pages/06.about/external.de.md b/pages/06.about/external.de.md new file mode 100644 index 0000000..4063290 --- /dev/null +++ b/pages/06.about/external.de.md @@ -0,0 +1,5 @@ +--- +title: Services +external_url: 'https://techsaviours.org/index-de.html#freeservices' +--- + diff --git a/pages/06.about/external.en.md b/pages/06.about/external.en.md new file mode 100644 index 0000000..14bb52b --- /dev/null +++ b/pages/06.about/external.en.md @@ -0,0 +1,5 @@ +--- +title: Services +external_url: 'https://techsaviours.org/#freeservices' +--- + diff --git a/pages/sidebar/about/blog.de.md b/pages/sidebar/about/blog.de.md new file mode 100755 index 0000000..ecf575c --- /dev/null +++ b/pages/sidebar/about/blog.de.md @@ -0,0 +1,27 @@ +--- +taxonomy: + category: + - sidebar +css_suffix: widget-about +surround: authorzo +image: org-favicon-white-background.png +social: + - + icon: 'fab fa-mastodon' + url: 'https://fosstodon.org/@techsaviours' + - + icon: 'fas fa-rss' + url: 'https://www.facebook.com/tech-saviours-nz' + - + icon: facebook + url: 'https://www.facebook.com/tech-saviours-nz' + - + icon: twitter + url: 'https://twitter.com/techsaviours_nz' + - + icon: youtube + url: 'https://www.youtube.com/channel/UCZt-OPPHkVrMl-k0cEKaGgQ' +media_order: org-favicon-white-background.png +--- + +###Alle unsere News werden über unsere Community-Medienkanäle verbreitet \ No newline at end of file diff --git a/pages/sidebar/about/blog.en.md b/pages/sidebar/about/blog.en.md new file mode 100644 index 0000000..c9eeb5c --- /dev/null +++ b/pages/sidebar/about/blog.en.md @@ -0,0 +1,27 @@ +--- +taxonomy: + category: + - sidebar +css_suffix: widget-about +surround: authorzo +image: org-favicon-white-background.png +social: + - + icon: 'fab fa-mastodon' + url: 'https://fosstodon.org/@techsaviours' + - + icon: 'fas fa-rss' + url: 'https://www.facebook.com/tech-saviours-nz' + - + icon: facebook + url: 'https://www.facebook.com/tech-saviours-nz' + - + icon: twitter + url: 'https://twitter.com/techsaviours_nz' + - + icon: youtube + url: 'https://www.youtube.com/channel/UCZt-OPPHkVrMl-k0cEKaGgQ' +media_order: org-favicon-white-background.png +--- + +###All our news are shared through our community media channels \ No newline at end of file diff --git a/pages/sidebar/about/org-favicon-white-background.png b/pages/sidebar/about/org-favicon-white-background.png new file mode 100644 index 0000000..9fc7467 Binary files /dev/null and b/pages/sidebar/about/org-favicon-white-background.png differ diff --git a/plugins/.gitkeep b/plugins/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/admin/.editorconfig b/plugins/admin/.editorconfig new file mode 100644 index 0000000..3b95e6d --- /dev/null +++ b/plugins/admin/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +# 2 space indentation +[*.{yaml,.yml}] +indent_size = 2 diff --git a/plugins/admin/.gitattributes b/plugins/admin/.gitattributes new file mode 100644 index 0000000..c5ef731 --- /dev/null +++ b/plugins/admin/.gitattributes @@ -0,0 +1,8 @@ +# Linguist Normalizer +*.yaml linguistic-language=PHP +*.twig linguistic-language=PHP +**/gulpfile.babel.js linguist-vendored +**/webpack.conf.js linguist-vendored +**/js/*.js linguist-vendored +**/js/*.json linguist-vendored +**/css-compiled/*.css linguist-vendored diff --git a/plugins/admin/.github/FUNDING.yml b/plugins/admin/.github/FUNDING.yml new file mode 100644 index 0000000..e84f52b --- /dev/null +++ b/plugins/admin/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: grav +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: # Replace with a single custom sponsorship URL diff --git a/plugins/admin/.gitignore b/plugins/admin/.gitignore new file mode 100644 index 0000000..77d3944 --- /dev/null +++ b/plugins/admin/.gitignore @@ -0,0 +1,16 @@ +themes/grav/.sass-cache +.DS_Store +crowdin.yaml + +# Node Modules +**/node_modules/** +themes/grav/js/admin.js +themes/grav/js/vendor.js +themes/grav/js/*.map +.idea + +tests/_output/* +tests/_support/_generated/* +tests/cache/* +tests/error.log +/crowdin.yaml diff --git a/plugins/admin/CHANGELOG.md b/plugins/admin/CHANGELOG.md new file mode 100644 index 0000000..d6fea9a --- /dev/null +++ b/plugins/admin/CHANGELOG.md @@ -0,0 +1,2427 @@ +# v1.10.25 +## 11/16/2021 + +3. [](#bugfix) + * Fixed unescaped messages in JSON responses + +# v1.10.24 +## 10/26/2021 + +1. [](#new) + * Require **Grav 1.7.24** +2. [](#improved) + * Use new `Http\Response` rather than deprecated `GPM\Response` +3. [](#bugfix) + * Fixed an issue with invalid HTML throwing errors on HTML security scanning + * Clear cache when installing plugins + +# v1.10.23 +## 09/29/2021 + +1. [](#new) + * Updated SCSS compiler to v1.8 +2. [](#improved) + * Updated with latest language strings from Crowdin.com +3. [](#bugfix) + * Fixed images from plugins/themes disappearing when saving twice + +# v1.10.22 +## 09/16/2021 + +1. [](#new) + * Updated SCSS compiler to v1.7 + +# v1.10.21 +## 09/14/2021 + +1. [](#new) + * Require **Grav 1.7.21** +2. [](#improved) + * Added a note about UTC times in scheduler AT syntax help + * Now using a monospaced text-based scheduler AT field in scheduler for simplicity + * Improved `Admin:data()` and `Admin::getConfigurationData()` to be more strict +3. [](#bugfix) + * Fixed configuration save location to point to existing config folder [#2176](https://github.com/getgrav/grav-plugin-admin/issues/2176) + +# v1.10.20 +## 09/01/2021 + +1. [](#bugfix) + * Fixed regression `Argument 4 passed to Grav\Plugin\Form\TwigExtension::prepareFormField() must be of the type array` [#2177](https://github.com/getgrav/grav-plugin-admin/issues/2177) + * Fixed `X-Frame-Options` to be `DENY` in all admin pages to prevent a clickjacking attack + +# v1.10.19 +## 08/31/2021 + +1. [](#new) + * Require **Grav 1.7.19** and **Form 5.1.0** and **Login 3.5.0** + * Updated SCSS compiler to v1.6 +2. [](#improved) + * Updated forms and nested fields to use new form logic + * Admin form now use layout `admin`, meaning you can create admin specific field templates by `forms/fields/myfield/admin-field.html.twig` + * Stop using `|tu` filter, Grav already has the same logic in `|t` for admin + * Remove unneeded escapes + * Allow removal of plugin when disabled [#2167](https://github.com/getgrav/grav-plugin-admin/issues/2167) +3. [](#bugfix) + * Fixed missing values in `fieldset` form field + +# v1.10.18 +## 07/19/2021 + +1. [](#improved) + * Add logic to allow fieldset form field inside a list field [#2159](https://github.com/getgrav/grav-plugin-admin/pull/2159) + +# v1.10.17 +## 06/15/2021 + +1. [](#improved) + * Added timestamp as title in logs date [#2141](https://github.com/getgrav/grav-plugin-admin/issues/2141) + * Use `base64_encode` filter rather than function + * Composer update +1. [](#bugfix) + * Fixed missing `Remove Theme` button when the theme is inactive + * Update taskGetChildTypes() to use Flex Pages (works without the plugin) [#2087](https://github.com/getgrav/grav-plugin-admin/issues/2087) + +# v1.10.16 +## 06/02/2021 + +1. [](#bugfix) + * Fixed issue with some elements overflowing closed list items [#2146](https://github.com/getgrav/grav-plugin-admin/issues/2146) + * Fixed configuration not fully updating on save [#2149](https://github.com/getgrav/grav-plugin-admin/issues/2149) + * Fixed display issue with "+ Add Page" and picking a different route [#2136](https://github.com/getgrav/grav-plugin-admin/issues/2136), [#2145](https://github.com/getgrav/grav-plugin-admin/issues/2145) + * Treat WebP as image when inserting / drag & dropping [#2150](https://github.com/getgrav/grav-plugin-admin/issues/2150) + +# v1.10.15 +## 05/19/2021 + +1. [](#new) + * Updated SCSS compiler to v1.5 +1. [](#improved) + * Updated node modules dev dependencies + * Package.json scripts cleanup + * Recompiled JS for production + * Use `base645_encode` filter rather than function + * Editor: Do not assume images URLs are going to be `http://` (wrong assumption plus not SSL) [#2127](https://github.com/getgrav/grav-plugin-admin/issues/2127) + * Improved Theme Activation + Plugin Enabled logic to ensure configuration is not displayed unless activation/enabled state. Fixes [#2140](https://github.com/getgrav/grav-plugin-admin/issues/2140) +1. [](#bugfix) + * Fixed issue with slugify where single curly quotes in titles would translate to straight single quote [#2101](https://github.com/getgrav/grav-plugin-admin/issues/2101) + * Fix z-index issue with fullscreeen editor (and toolips) [#2143](https://github.com/getgrav/grav-plugin-admin/issues/2143) + +# v1.10.14 +## 04/29/2021 + +1. [](#improved) + * Added a `min_height:` option for list field +1. [](#bugfix) + * Fixed z-index issue for tooltips in sidebar + * Fixed custom files being overridden during theme update [#2135](https://github.com/getgrav/grav-plugin-admin/issues/2135) + +# v1.10.13 +## 04/23/2021 + +1. [](#new) + * Added refresh action button for Folder to ease the regeneration of the slug based on the title. Available also as API entry `Grav.default.Forms.Fields.FolderField.Regenerate()` [#1738](https://github.com/getgrav/grav-plugin-admin/issues/1738) +1. [](#improved) + * Removed sourcemaps references from fork-awesome.min.css [#2122](https://github.com/getgrav/grav-plugin-admin/issues/2122) + * Support native spell checkers in CodeMirror editor [#1266](https://github.com/getgrav/grav-plugin-admin/issues/1266) + * Added new 'Content Highlight' color to presets + * Copying Pages now prompts a dedicated modal that allows for picking title, folder name, parent location, page template and visibility [#1738](https://github.com/getgrav/grav-plugin-admin/issues/1738) + * Updated with latest language translations from Crowdin.com +1. [](#bugfix) + * Moved preset CSS compile to earlier in the process to ensure compilation happens in time. + * Prevent Save actions from Flex Objects to trigger the unsaved unload notice [#2125](https://github.com/getgrav/grav-plugin-admin/issues/2125) + * Fixed audit vulnerabilities in module dependencies and house cleanup [#2096](https://github.com/getgrav/grav-plugin-admin/issues/2096) + * Fixed issue preventing Drag & Drop of media files while in Expert Mode [#1927](https://github.com/getgrav/grav-plugin-admin/issues/1927) + * Fixed broken link colors in `preset.css` which was causing issues with tabs and dropdowns + * Fixed permissions for page related tasks and actions + * Fixed permission check for configuration save [#2130](https://github.com/getgrav/grav-plugin-admin/issues/2130) + * Fixed missing/wrong page categories and tags when multi-language support is enabled [#2107](https://github.com/getgrav/grav-plugin-admin/issues/2107) + +# v1.10.12 +## 04/15/2021 + +1. [](#bugfix) + * Regression: Fixed broken plugin/theme installer in admin + * Fixed error reporting for AJAX tasks if user has no permissions + * Fixed missing slash in password reset URL [#2119](https://github.com/getgrav/grav-plugin-admin/issues/2119) + +# v1.10.11 +## 04/13/2021 + +1. [](#bugfix) + * **IMPORTANT** Fixed security vulnerability that allows installation of plugins with minimal admin privileges [GHSA-wg37-cf5x-55hq](https://github.com/getgrav/grav-plugin-admin/security/advisories/GHSA-wg37-cf5x-55hq) + * Fixed `You have been logged out` message when entering to 2FA authentication due to `/admin/task:getNotifications` AJAX call + * Fixed broken 2FA login when site is not configured to use Flex Users [#2109](https://github.com/getgrav/grav-plugin-admin/issues/2109) + * Fixed error message when user clicks logout link after the session has been expired + +# v1.10.10 +## 04/07/2021 + +1. [](#bugfix) + * Fixed missing `admin-preset.css` in multisite environments + * Regression: Fixed broken 2FA form [#2109](https://github.com/getgrav/grav-plugin-admin/issues/2109) + +# v1.10.9 +## 04/06/2021 + +1. [](#new) + * Requires **Grav 1.7.10** +1. [](#improved) + * Better isolate admin to prevent session related vulnerabilities + * Removed support for custom login redirects for improved security + * Shorten forgot password link lifetime from 7 days to 1 hour + * Updated with latest language translations from Crowdin.com +1. [](#bugfix) + * Fixed issue where Adding a new page and canceling from within Editing would alter the Parent location of the edited page [#2067](https://github.com/getgrav/grav-plugin-admin/issues/2067) + * Fixed and enhanced Range field to be Lists compatible [#2062](https://github.com/getgrav/grav-plugin-admin/issues/2062) + * Fixed ERR_TOO_MANY_REDIRECTS with HTTPS = 'On' [#2100](https://github.com/getgrav/grav-plugin-admin/issues/2100) + * Prevent expert editing mode from anyone else than super users [#2094](https://github.com/getgrav/grav-plugin-admin/issues/2094) + * Fixed login related pages being accessible from admin when user has logged in + * Fixed admin user creation and password reset allowing unsafe passwords + * Fixed missing validation when registering the first admin user + * Fixed reset password email not to have session specific token in it + * Fixed admin controller running before setting `$grav['page']` + +# v1.10.8 +## 03/19/2021 + +1. [](#improved) + * Include alt text and title for images added to the editor [#2098](https://github.com/getgrav/grav-plugin-admin/issues/2098) +1. [](#bugfix) + * Fixed issue replacing `wildcard` field names in flex collections [#2092](https://github.com/getgrav/grav-plugin-admin/pull/2092) + * Fixed legacy Pages having old `modular` reference instead of `module` [#2093](https://github.com/getgrav/grav-plugin-admin/issues/2093) + * Fixed issue where Add New modal would close if selecting an item outside of the modal window. It is now necessary go through the Cancel button and clicking the overlay won't trigger the closing of the modal [#2089](https://github.com/getgrav/grav-plugin-admin/issues/2089), [#2065](https://github.com/getgrav/grav-plugin-admin/issues/2065) + +# v1.10.7 +## 03/17/2021 + +1. [](#improved) + * Force height of Flex pages admin to fit available space + * Updated languages from Crowdin.com + * Better field type definitions for file, pagemedia, filepicker and pagemediafield +1. [](#bugfix) + * Fixed error when checking missing log file [#2088](https://github.com/getgrav/grav-plugin-admin/issues/2088) + +# v1.10.6 +## 02/23/2021 + +1. [](#new) + * Vastly improved support for RTL languages [#2078](https://github.com/getgrav/grav-plugin-admin/pull/2078) +1. [](#improved) + * Flex pages admin better uses available space [#2075](https://github.com/getgrav/grav/issues/2075) +1. [](#bugfix) + * Regression: Fixed enabling/disabling plugin or theme corrupting configuration + * Fixed unnecessary closing bracket causing JS error [#2079](https://github.com/getgrav/grav-plugin-admin/issues/2079) + * Fixed wrong language in Admin Tools [#2077](https://github.com/getgrav/grav-plugin-admin/issues/2077) + +# v1.10.5 +## 02/18/2021 + +1. [](#bugfix) + * Regression: Fixed fatal error in admin if POST request has `data` in it [#2074](https://github.com/getgrav/grav-plugin-admin/issues/2074) + * Fixed Admin creating empty `user/config/info.yaml` file (the file can be safely removed, it is not in use) + * Fixed ACL for users with mixed case usernames [#2073](https://github.com/getgrav/grav-plugin-admin/issues/2073) + +# v1.10.4 +## 02/17/2021 + +1. [](#new) + * Added support to include new page creation modals in other pages by using `form_action` twig variable [#2024](https://github.com/getgrav/grav-plugin-admin/pull/2024) + * Updated all languages from [Crowdin](https://crowdin.com/project/grav-admin) - Please update any translations here +1. [](#improved) + * Removed `noscript` template, because 2021... + * List field: added new `placement` property to decide wether to add new items at the top, bottom or based on the *position* of the clicked button [#2055](https://github.com/getgrav/grav-plugin-admin/pull/2055) + * Ensure admin default CSS styles load **first**, and presets loads **last** + * Tweaked handling of uploaded files [#1429](https://github.com/getgrav/grav-plugin-admin/issues/1429) + * Provide media object and filename in `onAdminAfterDelMedia` event [#1905](https://github.com/getgrav/grav-plugin-admin/pull/1905) +1. [](#bugfix) + * Fixed case-sensitive `accept` in `filepicker` field + * Fixed HTML Entities in titles [#2028](https://github.com/getgrav/grav-plugin-admin/issues/2028) + * Fixed deleting list field options completely, didn't save changes [#2056](https://github.com/getgrav/grav-plugin-admin/issues/2056) + * Fixed `onAdminAfterAddMedia` and `onAdminAfterDelMedia` events always pointing to the home page + * Fixed ACL for Configuration tabs [#771](https://github.com/getgrav/grav-plugin-admin/issues/771) + * Fixed changelog button showing up in Info page even if user cannot access it + * Fixed toggleable checkboxes being unchecked in fieldset columns [#2063](https://github.com/getgrav/grav-plugin-admin/issues/2063) + * Fixed issue with max backups of zero [#2070](https://github.com/getgrav/grav-plugin-admin/issues/2070) + +# v1.10.3 +## 02/01/2021 + +1. [](#new) + * Requires **Grav 1.7.4** (SemVer library moved to Grav) + * Added back special fonts (including Gantry) +2. [](#bugfix) + * Fixed field type `range` not taking into account legitimate `0` values + * Fixed `Call to a member function trackHit() on null` [#2049](https://github.com/getgrav/grav-plugin-admin/issues/2049) + +# v1.10.2 +## 01/21/2021 + +2. [](#bugfix) + * Fixed admin style compilation failing to save CSS if assets folder does not exist + +# v1.10.1 +## 01/20/2021 + +1. [](#improved) + * Added `watch.sh` for compiling SCSS with native sass compiler +2. [](#bugfix) + * Fixed issue with overlapping sidebar when using fullscreen editor [#2022](https://github.com/getgrav/grav-plugin-admin/issues/2022) + +# v1.10.0 +## 01/19/2021 + +1. [](#new) + * Requires **Grav 1.7 and PHP 7.3.6** + * Read about this release in the [Grav 1.7 Released](https://getgrav.org/blog/grav-1.7-released) blog post + * Read the full list of changes in the [Changelog on GitHub](https://github.com/getgrav/grav-plugin-admin/blob/1.10.0/CHANGELOG.md) + * Please read [Grav 1.7 Upgrade Guide](https://learn.getgrav.org/17/advanced/grav-development/grav-17-upgrade-guide) before upgrading! +1. [](#improved) + * Various notifications improvements +1. [](#bugfix) + * Fixed missed highlight on the selected page in Parents field + * Fixed notifications that would not be remembered as hidden + * Fixed taxonomy field not listing existing options in Flex Pages + * Fixed taxonomy field not working outside pages + * Fixed fatal error when moving a page using the old implementation [#2019](https://github.com/getgrav/grav-plugin-admin/issues/2019) + * Fixed evaluating default value in `hidden` field (thanks @NicoHood) + +# v1.10.0-rc.20 +## 12/14/2020 + +1. [](#improved) + * Cookies now explicitly set `SameSite` to `Lax` unless otherwise specified [#1998](https://github.com/getgrav/grav-plugin-admin/issues/1998) + * Exposed **Cookies** class (`Grav.default.Utils.Cookies`) for developers that need it in Admin. +1. [](#bugfix) + * Fixed Plugins references in Themes details page. + * Fixed issue preventing purchase of Themes within Admin and redirecting instead. + * Regression: Values inside Fieldset do not display [#1995](https://github.com/getgrav/grav-plugin-admin/issues/1995) + +# v1.10.0-rc.19 +## 12/02/2020 + +1. [](#improved) + * Just keeping sync with Grav rc.19 + +# v1.10.0-rc.18 +## 12/02/2020 + +1. [](#new) + * Retired "Secure Delete" and "Warn on page delete". You are now always warned and asked to confirm a deletion. +1. [](#improved) + * Auto-link a plugin/theme license in details if it starts with `http` + * Allow to fallback to `docs:` instead of `readme:` + * Forward a `sid` to GPM when downloading a premium package + * Better support for array field key/value when either key or value is stored empty [#1972](https://github.com/getgrav/grav-plugin-admin/issues/1972) + * Remember the open state of the sidebar [#1973](https://github.com/getgrav/grav-plugin-admin/issues/1973) + * Upgraded node dependencies to latest version. Improved speed of JS compilation. + * Added modal to confirm updating Grav as well as cool down counter before enabling Update button [#1257](https://github.com/getgrav/grav-plugin-admin/issues/1257) + * Better handling of offline/intranet mode when the repository index is missing. Faster admin. [#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916) + * Statistics is now Page View Statistics [#1885](https://github.com/getgrav/grav-plugin-admin/issues/1885) + * It is now possible to use regex as values for "Hide page types in Admin" and "Hide modular page types in Admin" settings [#1828](https://github.com/getgrav/grav-plugin-admin/issues/1828) + * Default to `disabled` state for all cron-jobs +1. [](#bugfix) + * Fixed Safari issue with new ACL picker field [#1955](https://github.com/getgrav/grav-plugin-admin/issues/1955) + * Stop propagation of ACL add button in ACL picker [flex-objects#83](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/83) + * Fixed missing special groups `authors` and `defaults` for pages + * Fixed Page Move action and selection highlight in Parents selector modal [flex-objects#80](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/80) + * Fixed folder auto-naming in Add Module [#1937](https://github.com/getgrav/grav-plugin-admin/issues/1937) + * Fixed remodal issue triggering close when selecting a dropdown item ending outside of scope [#1682](https://github.com/getgrav/grav-plugin-admin/issues/1682) + * Reworked how collapsed lists work so the tooltip is not cut off [#1928](https://github.com/getgrav/grav-plugin-admin/issues/1928) + * Fixed KeepAlive issue where too large of a session value would fire the keep alive immediately [#1860](https://github.com/getgrav/grav-plugin-admin/issues/1860) + * Fixed stringable objects breaking the inputs + * Fixed filepicker, pagemediaselect fields with `multiple: true` and `array: true` [#1580](https://github.com/getgrav/grav-plugin-admin/issues/1580) + +# v1.10.0-rc.17 +## 10/07/2020 + +1. [](#new) + * Support premium themes +1. [](#improved) + * Improved some error messages for better readability + * Strip tags from browser title +1. [](#bugfix) + * More multi-site routing fixes + * Fixed issue that would force a page reload when failing to install/update a plugin or theme. + * Fixed proxy/browser caching issues in admin pages + +# v1.10.0-rc.16 +## 09/01/2020 + +1. [](#improved) + * Made all the `onAdmin*` CRUD events to pass `object` (and backwards compatible `page`) to make them easier to use + * Updated vendor libraries including `SCSSPHP` to v1.2 +1. [](#bugfix) + * Fixed issue with File field being used in Theme/Plugins + * Fixed bad redirection after successful admin login in subdirectory multisite [#1487](https://github.com/getgrav/grav-plugin-admin/issues/1487) + +# v1.10.0-rc.15 +## 07/22/2020 + +1. [](#bugfix) + * Disabled the EXIF library for Dropzone for fixing the orientation as it was getting applied twice [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923) + * Forked Dropzone fo fix issue with Resize + Exif orientation [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923) + * Fixed URI encode for the preview of images names + +# v1.10.0-rc.14 +## 07/09/2020 + +1. [](#improved) + * Completely removed old Google font support for upgrade compatibility +1. [](#bugfix) + * Fixed bad `use` reference to `UserObject` + +# v1.10.0-rc.13 +## 07/01/2020 + +1. [](#improved) + * Improved color picker field + * Trim login route for safety + * Composer update to grab latest vendor libs + +# v1.10.0-rc.12 +## 06/08/2020 + +1. [](#new) + * Added ability to set a preferred markdown editor in user profile + * Added new `onAdminListContentEditors` event to add a custom editor to the list of available +1. [](#bugfix) + * Fixed issue deleting file from a plugin's configuration + * Use `Pages::find()` instead of `Pages::dispatch()` as we do not want to redirect out of admin + * Fixed broken `parent` field when using the old pages + * Fixed broken `file` field preview when using streams in the path + +# v1.10.0-rc.11 +## 05/14/2020 + +1. [](#new) + * Major enhancements to "White Label" functionality including ability to export/import presets + * New horizontal scroller for theme presets + * Codemirror Fontsize / Preset / Font preference options +1. [](#improved) + * Fixed lots of styling issues related to "White Label" presets + * Changed out "One Light" theme for new "Firewatch Light" theme + * New scrolling system based on `SimpleBar` + native CSS scrollbar styling + +# v1.10.0-rc.10 +## 04/30/2020 + +1. [](#new) + * Addd new `taskConvertUrls` method for use with 3rd party editors + +# v1.10.0-rc.9 +## 04/27/2020 + +1. [](#new) + * Added new "White Label" functionality to customize admin colors + logos + * Added badge count for children in the Parents field +1. [](#improved) + * Added markdown support to `text` in `section` field +1. [](#bugfix) + * Prevent loading Pages in Parents field if they don't have children + * Fixed custom folder in `mediapicker` field not working with streams + * Fixed language redirect adding extra language prefix in Flex + * Fixed `Invalid input in "Parent"` when saving page in raw mode [#1869](https://github.com/getgrav/grav-plugin-admin/issues/1869) + +# v1.10.0-rc.8 +## 03/19/2020 + +1. [](#new) + * Added `has-children` flag in parent field data response + * Added `RESET` en lang string +1. [](#bugfix) + * Fixed parent field not working with regular pages + +# v1.10.0-rc.7 +## 03/05/2020 + +1. [](#new) + * Enable admin cache by default (for existing sites, check `Plugins > Admin Panel > Enable Admin Caching`) +1. [](#improved) + * Removed old `scss.sh` and `watch.sh` scripts, use `gulp watch-css` + * Added keysOnly parameter to `AdminPlugin::pagesTypes()` and `AdminPlugin::pagesModularTypes()` methods + * Added ignore parameter to `Admin::types()` and `Admin::modularTypes()` methods + * Improved configuration fields for hiding page types in Admin +1. [](#bugfix) + * Fixed minor UI padding in Flex pages [#1825](https://github.com/getgrav/grav-plugin-admin/issues/1825) + * Fixed `column` and `section` fields loosing user entered value when form submit fails + * Fixed `order` field not working with a new Flex Page + +# v1.10.0-rc.6 +## 02/11/2020 + +1. [](#new) + * Pass phpstan level 1 tests + * Updated semver library to v1.5 + * Require flex-objects plugin +1. [](#improved) + * Added some debugging messages (turned off by default) + +# v1.10.0-rc.5 +## 02/03/2020 + +1. [](#new) + * No changes, just keeping things in sync with Grav RC version + +# v1.10.0-rc.4 +## 02/03/2020 + +1. [](#new) + * Added message to dashboard to install Flex Objects plugin if it is missing + * Updated `permissions` field to use new `$grav['permissions']` + * DEPRECATED `onAdminRegisterPermissions` event, use `PermissionsRegisterEvent::class` event instead + * DEPRECATED `Admin::setPermissions()` and `Admin::addPermissions()`, use `PermissionsRegisterEvent::class` event instead + * DEPRECATED `Admin::getPermissions()`, use `$grav['permissions']->getInstances()` instead +1. [](#improved) + * Added `field.show_label` and `field.label` display logic from frontend forms +1. [](#bugfix) + * Fixed user profile when using `Flex Users` only in admin + * Fixed saving data with empty field, default value (from config, plugin, theme) was used instead + * Fixed JS bug is using empty Grav URI param key + * Fixed bug in toggleable field being disabled with empty value (`''` `0`, `false`, `[]`...) + * Fixed `admin_route()` twig function to work properly with Grav 1.7.0-rc.4, which fixes `Route` base + * Fixed misleading 'Show sensitive data' configuration option wording [#1818](https://github.com/getgrav/grav-plugin-admin/issues/1818) + +# v1.10.0-rc.3 +## 01/02/2020 + +1. [](#new) + * Added ability to display **Changelogs** for `Grav`, `Plugins` and `Themes` + * Added raw root page support for `Flex Pages` + +# v1.10.0-rc.2 +## 12/04/2019 + +1. [](#new) + * Added support for hiding parts of admin by `Deny` permissions (`Flex Users` only) + * Optimized `parent` field for Flex Pages +1. [](#improved) + * Improved `permissions` field to add support for displaying calculated permissions + * Grav 1.7: Updated deprecated `$page->modular()` method calls to `$page->isModule()` + * Output the current process user name in Scheduler instructions + * Translations: rename MODULAR to MODULE everywhere +1. [](#bugfix) + * Fixed `permissions` field with nested permissions + * Fixed Save Shortcut (CTRL + S / CMD + S) not working with new Flex Pages [#1787](https://github.com/getgrav/grav-plugin-admin/issues/1787) + +# v1.10.0-rc.1 +## 11/06/2019 + +1. [](#new) + * Added a new `onAdminLogFiles()` event for 3rd party plugins to register log files for log viewer [#1765](https://github.com/getgrav/grav-plugin-admin/issues/1765) +1. [](#improved) + * Improved delete button UI [#1769](https://github.com/getgrav/grav-plugin-admin/issues/1769) + * Ability to configure display of 3rd party dashboard widgets [#1766](https://github.com/getgrav/grav-plugin-admin/issues/1766) +1. [](#bugfix) + * Fixed administrator user creation when `Flex Users` is enabled + * Fixed minor button alignment in FF [#1760](https://github.com/getgrav/grav-plugin-admin/issues/1760) + +# v1.10.0-beta.10 +## 10/03/2019 + +1. [](#bugfix) + * Regression: Fixed language assignments for the pages without set language + +# v1.10.0-beta.9 +## 09/26/2019 + +1. [](#bugfix) + * Make pages field to work with Flex Pages + +# v1.10.0-beta.8 +## 09/19/2019 + +1. [](#new) + * Add ability to Sanitize SVGs on file upload + * Add ability to Sanitize SVGs in Page media +1. [](#improved) + * YAML linter report now supports multi-language + * Better colors/placement of toolbar buttons in page edit view +1. [](#bugfix) + * Fixed missing language for AJAX requests + * Fixed redirect with absolute language URL + * Fixed issue with user avatar reference not being deleted when image removed + +# v1.10.0-beta.7 +## 08/30/2019 + +1. [](#bugfix) + * Fixed regression: Do not require Flex Objects plugin [grav#2653](https://github.com/getgrav/grav/issues/2653) + +# v1.10.0-beta.6 +## 08/29/2019 + +1. [](#improved) + * Optimized admin for speed (only load frontend pages on demand) + * Updated navigation menu to be fully controlled and overrideable by `onAdminMenu` event + * Lots of Flex Page speed improvements + +# v1.10.0-beta.5 +## 08/11/2019 + +1. [](#new) + * Added `data()` twig function to create data object from an array +1. [](#improved) + * Better support for `array` field into `list` field + * Made RAW blueprints (expert mode) to work properly with Flex Form + * Better support for `clockwork` logs +1. [](#bugfix) + * Fixed issue with nested `list` fields both utilizing the custom `key` functionality + * Regression: Page Preview not working, bad url [#1715](https://github.com/getgrav/grav-plugin-admin/issues/1715) + * Fixed '+New Folder' to work with new parent picker + * Fixed missing XSS check field when editing modular page as raw + * Fixed minor CSS layout issue [#1717](https://github.com/getgrav/grav-plugin-admin/issues/1717) + +# v1.10.0-beta.4 +## 07/01/2019 + +1. [](#new) + * Added `Admin::redirect()` method to allow redirects to be used outside of controllers + * Added `$admin->adminRoute()` method and `admin_route()` twig function to create language aware admin page links + * Renamed `Admin::route()` to `Admin::getCurrentRoute()` and deprecated the old call +1. [](#improved) + * Much improved multi-language support for pages + * Admin redirects should now work better with multiple languages enabled +1. [](#bugfix) + * Fixed default language being renamed to `page.en.md` (English) instead of keeping existing `page.md` filename + * Fixed possibility to override already existing translation by `Save As Language` + * Fixed missing default translation if page used plain `.md` file extension without language code + * Fixed wrong translation showing up as page fallback language + * Integrated Admin 1.9.8 bug fixes + +# v1.10.0-beta.3 +## 06/24/2019 + +1. [](#improved) + * Smarter handling of symlinks in parent field +1. [](#bugfix) + * Fixed issue with windows paths in `parent` field [#1699](https://github.com/getgrav/grav-plugin-admin/issues/1699) + +# v1.10.0-beta.2 +## 06/21/2019 + +1. [](#improved) + * Moved Remodal in-house and added support for stackable modals [#1698](https://github.com/getgrav/grav-plugin-admin/issues/1698), [#1699](https://github.com/getgrav/grav-plugin-admin/issues/1699) +1. [](#bugfix) + * Fixed missing check for maximum allowed files in `files` field + +# v1.10.0-beta.1 +## 06/14/2019 + +1. [](#new) + * New Parent/Move field using Ajax for better performance + * Improvements to cache clearing when admin cache is enabled + * Require Grav v1.7 + * Use PSR-4 for plugin classes + * Added support for Twig 2.11 (compatible with Twig 1.40+) +1. [](#improved) + * Various admin performance improvements +1. [](#bugfix) + * Fixed admin caching issues + +# v1.9.19 +## 12/14/2020 + +1. [](#bugfix) + * Fixed `pages` field escaping issues, needs Grav update, too [#1990](https://github.com/getgrav/grav-plugin-admin/issues/1990) + * Fixed Plugins references in Themes details page. + * Fixed issue preventing purchase of Themes within Admin and redirecting instead. + * Fixed Page Picker not passing admin token + +# v1.9.18 +## 12/02/2020 + +1. [](#new) + * Never allow Admin pages to be rendered in ``, `'); + return true; + } + + return false; + } + + /** + * Generate HTML from item URL + * + * @access public + * @param Item $item + * @return bool + */ + public function generateHtmlFromUrl(Item $item) + { + if (preg_match('/youtube\.com\/watch\?v=(.*)/', $item->getUrl(), $matches)) { + $item->setContent(''); + return true; + } + + return false; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Logging/Logger.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Logging/Logger.php new file mode 100644 index 0000000..caec463 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Logging/Logger.php @@ -0,0 +1,114 @@ +format('Y-m-d H:i:s').'] '.$message; + } + } + + /** + * Get all logged messages. + * + * @static + * + * @return array + */ + public static function getMessages() + { + return self::$messages; + } + + /** + * Remove all logged messages. + * + * @static + */ + public static function deleteMessages() + { + self::$messages = array(); + } + + /** + * Set a different timezone. + * + * @static + * + * @see http://php.net/manual/en/timezones.php + * + * @param string $timezone Timezone + */ + public static function setTimeZone($timezone) + { + self::$timezone = $timezone ?: self::$timezone; + } + + /** + * Get all messages serialized into a string. + * + * @static + * + * @return string + */ + public static function toString() + { + return implode(PHP_EOL, self::$messages).PHP_EOL; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Atom.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Atom.php new file mode 100644 index 0000000..0496869 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Atom.php @@ -0,0 +1,395 @@ + 'http://www.w3.org/2005/Atom', + ); + + /** + * Get the path to the items XML tree. + * + * @param SimpleXMLElement $xml Feed xml + * @return SimpleXMLElement[] + */ + public function getItemsTree(SimpleXMLElement $xml) + { + return XmlParser::getXPathResult($xml, 'atom:entry', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'entry'); + } + + /** + * Find the feed url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedUrl(SimpleXMLElement $xml, Feed $feed) + { + $feed->setFeedUrl($this->getUrl($xml, 'self')); + } + + /** + * Find the site url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findSiteUrl(SimpleXMLElement $xml, Feed $feed) + { + $feed->setSiteUrl($this->getUrl($xml, 'alternate', true)); + } + + /** + * Find the feed description. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDescription(SimpleXMLElement $xml, Feed $feed) + { + $description = XmlParser::getXPathResult($xml, 'atom:subtitle', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'subtitle'); + + $feed->setDescription(XmlParser::getValue($description)); + } + + /** + * Find the feed logo url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLogo(SimpleXMLElement $xml, Feed $feed) + { + $logo = XmlParser::getXPathResult($xml, 'atom:logo', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'logo'); + + $feed->setLogo(XmlParser::getValue($logo)); + } + + /** + * Find the feed icon. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedIcon(SimpleXMLElement $xml, Feed $feed) + { + $icon = XmlParser::getXPathResult($xml, 'atom:icon', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'icon'); + + $feed->setIcon(XmlParser::getValue($icon)); + } + + /** + * Find the feed title. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedTitle(SimpleXMLElement $xml, Feed $feed) + { + $title = XmlParser::getXPathResult($xml, 'atom:title', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'title'); + + $feed->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $feed->getSiteUrl()); + } + + /** + * Find the feed language. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLanguage(SimpleXMLElement $xml, Feed $feed) + { + $language = XmlParser::getXPathResult($xml, '*[not(self::atom:entry)]/@xml:lang', $this->namespaces) + ?: XmlParser::getXPathResult($xml, '@xml:lang'); + + $feed->setLanguage(XmlParser::getValue($language)); + } + + /** + * Find the feed id. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedId(SimpleXMLElement $xml, Feed $feed) + { + $id = XmlParser::getXPathResult($xml, 'atom:id', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'id'); + + $feed->setId(XmlParser::getValue($id)); + } + + /** + * Find the feed date. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDate(SimpleXMLElement $xml, Feed $feed) + { + $updated = XmlParser::getXPathResult($xml, 'atom:updated', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'updated'); + + $feed->setDate($this->getDateParser()->getDateTime(XmlParser::getValue($updated))); + } + + /** + * Find the item published date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemPublishedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $date = XmlParser::getXPathResult($entry, 'atom:published', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'published'); + + $item->setPublishedDate(!empty($date) ? $this->getDateParser()->getDateTime((string) current($date)) : null); + } + + /** + * Find the item updated date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemUpdatedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $date = XmlParser::getXPathResult($entry, 'atom:updated', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'updated'); + + $item->setUpdatedDate(!empty($date) ? $this->getDateParser()->getDateTime((string) current($date)) : null); + } + + /** + * Find the item title. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + */ + public function findItemTitle(SimpleXMLElement $entry, Item $item) + { + $title = XmlParser::getXPathResult($entry, 'atom:title', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'title'); + + $item->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $item->getUrl()); + } + + /** + * Find the item author. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthor(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + $author = XmlParser::getXPathResult($entry, 'atom:author/atom:name', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'author/name') + ?: XmlParser::getXPathResult($xml, 'atom:author/atom:name', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'author/name'); + + $item->setAuthor(XmlParser::getValue($author)); + } + + /** + * Find the item author URL. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthorUrl(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + $authorUrl = XmlParser::getXPathResult($entry, 'atom:author/atom:uri', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'author/uri') + ?: XmlParser::getXPathResult($xml, 'atom:author/atom:uri', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'author/uri'); + + $item->setAuthorUrl(XmlParser::getValue($authorUrl)); + } + + /** + * Find the item content. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemContent(SimpleXMLElement $entry, Item $item) + { + $item->setContent($this->getContent($entry)); + } + + /** + * Find the item URL. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemUrl(SimpleXMLElement $entry, Item $item) + { + $item->setUrl($this->getUrl($entry, 'alternate', true)); + } + + /** + * Genereate the item id. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemId(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $id = XmlParser::getXPathResult($entry, 'atom:id', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'id'); + + if (!empty($id)) { + $item->setId($this->generateId(XmlParser::getValue($id))); + } else { + $item->setId($this->generateId( + $item->getTitle(), $item->getUrl(), $item->getContent() + )); + } + } + + /** + * Find the item enclosure. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemEnclosure(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $enclosure = $this->findLink($entry, 'enclosure'); + + if ($enclosure) { + $item->setEnclosureUrl(Url::resolve((string) $enclosure['href'], $feed->getSiteUrl())); + $item->setEnclosureType((string) $enclosure['type']); + } + } + + /** + * Find the item language. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $language = XmlParser::getXPathResult($entry, './/@xml:lang'); + $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); + } + + /** + * Find the item categories. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param Feed $feed Feed object + */ + public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $categories = XmlParser::getXPathResult($entry, 'atom:category/@term', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'category/@term'); + $item->setCategoriesFromXml($categories); + } + + /** + * Get the URL from a link tag. + * + * @param SimpleXMLElement $xml XML tag + * @param string $rel Link relationship: alternate, enclosure, related, self, via + * @return string + */ + private function getUrl(SimpleXMLElement $xml, $rel, $fallback = false) + { + $link = $this->findLink($xml, $rel); + + if ($link) { + return (string) $link['href']; + } + + if ($fallback) { + $link = $this->findLink($xml, ''); + return $link ? (string) $link['href'] : ''; + } + + return ''; + } + + /** + * Get a link tag that match a relationship. + * + * @param SimpleXMLElement $xml XML tag + * @param string $rel Link relationship: alternate, enclosure, related, self, via + * @return SimpleXMLElement|null + */ + private function findLink(SimpleXMLElement $xml, $rel) + { + $links = XmlParser::getXPathResult($xml, 'atom:link', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'link'); + + foreach ($links as $link) { + if ($rel === (string) $link['rel']) { + return $link; + } + } + + return null; + } + + /** + * Get the entry content. + * + * @param SimpleXMLElement $entry XML Entry + * @return string + */ + private function getContent(SimpleXMLElement $entry) + { + $content = current( + XmlParser::getXPathResult($entry, 'atom:content', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'content') + ); + + if (!empty($content) && count($content->children())) { + $xml_string = ''; + + foreach ($content->children() as $child) { + $xml_string .= $child->asXML(); + } + + return $xml_string; + } elseif (trim((string) $content) !== '') { + return (string) $content; + } + + $summary = XmlParser::getXPathResult($entry, 'atom:summary', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'summary'); + + return (string) current($summary); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/DateParser.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/DateParser.php new file mode 100644 index 0000000..0e5b80e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/DateParser.php @@ -0,0 +1,128 @@ + length ]. + * + * @var array + */ + public $formats = array( + DATE_ATOM => null, + DATE_RSS => null, + DATE_COOKIE => null, + DATE_ISO8601 => null, + DATE_RFC822 => null, + DATE_RFC850 => null, + DATE_RFC1036 => null, + DATE_RFC1123 => null, + DATE_RFC2822 => null, + DATE_RFC3339 => null, + 'l, d M Y H:i:s' => null, + 'D, d M Y H:i:s' => 25, + 'D, d M Y h:i:s' => 25, + 'D M d Y H:i:s' => 24, + 'j M Y H:i:s' => 20, + 'Y-m-d H:i:s' => 19, + 'Y-m-d\TH:i:s' => 19, + 'd/m/Y H:i:s' => 19, + 'D, d M Y' => 16, + 'Y-m-d' => 10, + 'd-m-Y' => 10, + 'm-d-Y' => 10, + 'd.m.Y' => 10, + 'm.d.Y' => 10, + 'd/m/Y' => 10, + 'm/d/Y' => 10, + ); + + /** + * Try to parse all date format for broken feeds. + * + * @param string $value Original date format + * + * @return DateTime + */ + public function getDateTime($value) + { + $value = trim($value); + + foreach ($this->formats as $format => $length) { + $truncated_value = $value; + if ($length !== null) { + $truncated_value = substr($truncated_value, 0, $length); + } + + $date = $this->getValidDate($format, $truncated_value); + if ($date !== false) { + return $date; + } + } + + return $this->getCurrentDateTime(); + } + + /** + * Get a valid date from a given format. + * + * @param string $format Date format + * @param string $value Original date value + * + * @return DateTime|bool + */ + public function getValidDate($format, $value) + { + $date = DateTime::createFromFormat($format, $value, $this->getTimeZone()); + + if ($date !== false) { + $errors = DateTime::getLastErrors(); + + if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) { + return $date; + } + } + + return false; + } + + /** + * Get the current datetime. + * + * @return DateTime + */ + public function getCurrentDateTime() + { + return new DateTime('now', $this->getTimeZone()); + } + + /** + * Get DateTimeZone instance + * + * @access public + * @return DateTimeZone + */ + public function getTimeZone() + { + return new DateTimeZone($this->config->getTimezone() ?: $this->timezone); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Feed.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Feed.php new file mode 100644 index 0000000..a56e71c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Feed.php @@ -0,0 +1,315 @@ +$property.PHP_EOL; + } + + $output .= 'Feed::date = '.$this->date->format(DATE_RFC822).PHP_EOL; + $output .= 'Feed::isRTL() = '.($this->isRTL() ? 'true' : 'false').PHP_EOL; + $output .= 'Feed::items = '.count($this->items).' items'.PHP_EOL; + + foreach ($this->items as $item) { + $output .= '----'.PHP_EOL; + $output .= $item; + } + + return $output; + } + + /** + * Get title. + */ + public function getTitle() + { + return $this->title; + } + + /** + * Get description. + */ + public function getDescription() + { + return $this->description; + } + + /** + * Get the logo url. + */ + public function getLogo() + { + return $this->logo; + } + + /** + * Get the icon url. + */ + public function getIcon() + { + return $this->icon; + } + + /** + * Get feed url. + */ + public function getFeedUrl() + { + return $this->feedUrl; + } + + /** + * Get site url. + */ + public function getSiteUrl() + { + return $this->siteUrl; + } + + /** + * Get date. + */ + public function getDate() + { + return $this->date; + } + + /** + * Get language. + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Get id. + */ + public function getId() + { + return $this->id; + } + + /** + * Get feed items. + */ + public function getItems() + { + return $this->items; + } + + /** + * Return true if the feed is "Right to Left". + * + * @return bool + */ + public function isRTL() + { + return Parser::isLanguageRTL($this->language); + } + + /** + * Set feed items. + * + * @param Item[] $items + * @return Feed + */ + public function setItems(array $items) + { + $this->items = $items; + return $this; + } + + /** + * Set feed id. + * + * @param string $id + * @return Feed + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Set feed title. + * + * @param string $title + * @return Feed + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * Set feed description. + * + * @param string $description + * @return Feed + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + + /** + * Set feed url. + * + * @param string $feedUrl + * @return Feed + */ + public function setFeedUrl($feedUrl) + { + $this->feedUrl = $feedUrl; + return $this; + } + + /** + * Set feed website url. + * + * @param string $siteUrl + * @return Feed + */ + public function setSiteUrl($siteUrl) + { + $this->siteUrl = $siteUrl; + return $this; + } + + /** + * Set feed date. + * + * @param \DateTime $date + * @return Feed + */ + public function setDate($date) + { + $this->date = $date; + return $this; + } + + /** + * Set feed language. + * + * @param string $language + * @return Feed + */ + public function setLanguage($language) + { + $this->language = $language; + return $this; + } + + /** + * Set feed logo. + * + * @param string $logo + * @return Feed + */ + public function setLogo($logo) + { + $this->logo = $logo; + return $this; + } + + /** + * Set feed icon. + * + * @param string $icon + * @return Feed + */ + public function setIcon($icon) + { + $this->icon = $icon; + return $this; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Item.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Item.php new file mode 100644 index 0000000..98214b8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Item.php @@ -0,0 +1,562 @@ +namespaces); + } + + /** + * Get specific XML tag or attribute value. + * + * @param string $tag Tag name (examples: guid, media:content) + * @param string $attribute Tag attribute + * + * @return array|false Tag values or error + */ + public function getTag($tag, $attribute = '') + { + if ($attribute !== '') { + $attribute = '/@'.$attribute; + } + + $query = './/'.$tag.$attribute; + $elements = XmlParser::getXPathResult($this->xml, $query, $this->namespaces); + + if ($elements === false) { // xPath error + return false; + } + + return array_map(function ($element) { return (string) $element;}, $elements); + } + + /** + * Return item information. + * + * @return string + */ + public function __toString() + { + $output = ''; + + foreach (array('id', 'title', 'url', 'language', 'author', 'enclosureUrl', 'enclosureType') as $property) { + $output .= 'Item::'.$property.' = '.$this->$property.PHP_EOL; + } + + $publishedDate = $this->publishedDate != null ? $this->publishedDate->format(DATE_RFC822) : null; + $updatedDate = $this->updatedDate != null ? $this->updatedDate->format(DATE_RFC822) : null; + + $categoryString = $this->categories != null ? implode(',', $this->categories) : null; + + $output .= 'Item::date = '.$this->date->format(DATE_RFC822).PHP_EOL; + $output .= 'Item::publishedDate = '.$publishedDate.PHP_EOL; + $output .= 'Item::updatedDate = '.$updatedDate.PHP_EOL; + $output .= 'Item::isRTL() = '.($this->isRTL() ? 'true' : 'false').PHP_EOL; + $output .= 'Item::categories = ['.$categoryString.']'.PHP_EOL; + $output .= 'Item::content = '.strlen($this->content).' bytes'.PHP_EOL; + + return $output; + } + + /** + * Get title. + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Get URL + * + * @access public + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set URL + * + * @access public + * @param string $url + * @return Item + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * Get id. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Get date. + * + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * Get published date. + * + * @return \DateTime + */ + public function getPublishedDate() + { + return $this->publishedDate; + } + + /** + * Get updated date. + * + * @return \DateTime + */ + public function getUpdatedDate() + { + return $this->updatedDate; + } + + /** + * Get content. + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * Set content + * + * @access public + * @param string $value + * @return Item + */ + public function setContent($value) + { + $this->content = $value; + return $this; + } + + /** + * Get enclosure url. + * + * @return string + */ + public function getEnclosureUrl() + { + return $this->enclosureUrl; + } + + /** + * Get enclosure type. + * + * @return string + */ + public function getEnclosureType() + { + return $this->enclosureType; + } + + /** + * Get language. + * + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Get categories. + * + * @return array + */ + public function getCategories() + { + return $this->categories; + } + + /** + * Get author. + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Get author URL. + * + * @return string + */ + public function getAuthorUrl() + { + return $this->authorUrl; + } + + /** + * Return true if the item is "Right to Left". + * + * @return bool + */ + public function isRTL() + { + return Parser::isLanguageRTL($this->language); + } + + /** + * Set item id. + * + * @param string $id + * @return Item + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Set item title. + * + * @param string $title + * @return Item + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * Set author. + * + * @param string $author + * @return Item + */ + public function setAuthor($author) + { + $this->author = $author; + return $this; + } + + /** + * Set author URL. + * + * @param string $authorUrl + * @return Item + */ + public function setAuthorUrl($authorUrl) + { + $this->authorUrl = $authorUrl; + return $this; + } + + /** + * Set item date. + * + * @param \DateTime $date + * @return Item + */ + public function setDate($date) + { + $this->date = $date; + return $this; + } + + /** + * Set item published date. + * + * @param \DateTime $publishedDate + * @return Item + */ + public function setPublishedDate($publishedDate) + { + $this->publishedDate = $publishedDate; + return $this; + } + + /** + * Set item updated date. + * + * @param \DateTime $updatedDate + * @return Item + */ + public function setUpdatedDate($updatedDate) + { + $this->updatedDate = $updatedDate; + return $this; + } + + /** + * Set enclosure url. + * + * @param string $enclosureUrl + * @return Item + */ + public function setEnclosureUrl($enclosureUrl) + { + $this->enclosureUrl = $enclosureUrl; + return $this; + } + + /** + * Set enclosure type. + * + * @param string $enclosureType + * @return Item + */ + public function setEnclosureType($enclosureType) + { + $this->enclosureType = $enclosureType; + return $this; + } + + /** + * Set item language. + * + * @param string $language + * @return Item + */ + public function setLanguage($language) + { + $this->language = $language; + return $this; + } + + /** + * Set item categories. + * + * @param array $categories + * @return Item + */ + public function setCategories($categories) + { + $this->categories = $categories; + return $this; + } + + /** + * Set item categories from xml. + * + * @param |SimpleXMLElement[] $categories + * @return Item + */ + public function setCategoriesFromXml($categories) + { + if ($categories !== false) { + $this->setCategories( + array_map( + function ($element) { + return trim((string) $element); + }, + $categories + ) + ); + } + + return $this; + } + + /** + * Set raw XML. + * + * @param \SimpleXMLElement $xml + * @return Item + */ + public function setXml($xml) + { + $this->xml = $xml; + return $this; + } + + /** + * Get raw XML. + * + * @return \SimpleXMLElement + */ + public function getXml() + { + return $this->xml; + } + + /** + * Set XML namespaces. + * + * @param array $namespaces + * @return Item + */ + public function setNamespaces($namespaces) + { + $this->namespaces = $namespaces; + return $this; + } + + /** + * Get XML namespaces. + * + * @return array + */ + public function getNamespaces() + { + return $this->namespaces; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/MalformedXmlException.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/MalformedXmlException.php new file mode 100644 index 0000000..efaf0ff --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/MalformedXmlException.php @@ -0,0 +1,13 @@ +fallback_url = $fallback_url; + $xml_encoding = XmlParser::getEncodingFromXmlTag($content); + + // Strip XML tag to avoid multiple encoding/decoding in the next XML processing + $this->content = Filter::stripXmlTag($content); + + // Encode everything in UTF-8 + Logger::setMessage(get_called_class().': HTTP Encoding "'.$http_encoding.'" ; XML Encoding "'.$xml_encoding.'"'); + $this->content = Encoding::convert($this->content, $xml_encoding ?: $http_encoding); + + $this->itemPostProcessor = new ItemPostProcessor($this->config); + $this->itemPostProcessor->register(new ContentGeneratorProcessor($this->config)); + $this->itemPostProcessor->register(new ContentFilterProcessor($this->config)); + } + + /** + * Parse the document. + * @return Feed + * @throws MalformedXmlException + */ + public function execute() + { + Logger::setMessage(get_called_class().': begin parsing'); + + $xml = XmlParser::getSimpleXml($this->content); + + if ($xml === false) { + Logger::setMessage(get_called_class().': Applying XML workarounds'); + $this->content = Filter::normalizeData($this->content); + $xml = XmlParser::getSimpleXml($this->content); + + if ($xml === false) { + Logger::setMessage(get_called_class().': XML parsing error'); + Logger::setMessage(XmlParser::getErrors()); + throw new MalformedXmlException('XML parsing error'); + } + } + + $this->used_namespaces = $xml->getNamespaces(true); + $xml = $this->registerSupportedNamespaces($xml); + + $feed = new Feed(); + + $this->findFeedUrl($xml, $feed); + $this->checkFeedUrl($feed); + + $this->findSiteUrl($xml, $feed); + $this->checkSiteUrl($feed); + + $this->findFeedTitle($xml, $feed); + $this->findFeedDescription($xml, $feed); + $this->findFeedLanguage($xml, $feed); + $this->findFeedId($xml, $feed); + $this->findFeedDate($xml, $feed); + $this->findFeedLogo($xml, $feed); + $this->findFeedIcon($xml, $feed); + + foreach ($this->getItemsTree($xml) as $entry) { + $entry = $this->registerSupportedNamespaces($entry); + + $item = new Item(); + $item->xml = $entry; + $item->namespaces = $this->used_namespaces; + + $this->findItemAuthor($xml, $entry, $item); + $this->findItemAuthorUrl($xml, $entry, $item); + + $this->findItemUrl($entry, $item); + $this->checkItemUrl($feed, $item); + + $this->findItemTitle($entry, $item); + $this->findItemContent($entry, $item); + + // Id generation can use the item url/title/content (order is important) + $this->findItemId($entry, $item, $feed); + $this->findItemDate($entry, $item, $feed); + $this->findItemEnclosure($entry, $item, $feed); + $this->findItemLanguage($entry, $item, $feed); + $this->findItemCategories($entry, $item, $feed); + + $this->itemPostProcessor->execute($feed, $item); + $feed->items[] = $item; + } + + Logger::setMessage(get_called_class().PHP_EOL.$feed); + + return $feed; + } + + /** + * Check if the feed url is correct. + * + * @param Feed $feed Feed object + */ + public function checkFeedUrl(Feed $feed) + { + if ($feed->getFeedUrl() === '') { + $feed->feedUrl = $this->fallback_url; + } else { + $feed->feedUrl = Url::resolve($feed->getFeedUrl(), $this->fallback_url); + } + } + + /** + * Check if the site url is correct. + * + * @param Feed $feed Feed object + */ + public function checkSiteUrl(Feed $feed) + { + if ($feed->getSiteUrl() === '') { + $feed->siteUrl = Url::base($feed->getFeedUrl()); + } else { + $feed->siteUrl = Url::resolve($feed->getSiteUrl(), $this->fallback_url); + } + } + + /** + * Check if the item url is correct. + * + * @param Feed $feed Feed object + * @param Item $item Item object + */ + public function checkItemUrl(Feed $feed, Item $item) + { + $item->url = Url::resolve($item->getUrl(), $feed->getSiteUrl()); + } + + /** + * Find the item date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $this->findItemPublishedDate($entry, $item, $feed); + $this->findItemUpdatedDate($entry, $item, $feed); + + if ($item->getPublishedDate() === null) { + // Use the updated date if available, otherwise use the feed date + $item->setPublishedDate($item->getUpdatedDate() ?: $feed->getDate()); + } + + if ($item->getUpdatedDate() === null) { + // Use the published date as fallback + $item->setUpdatedDate($item->getPublishedDate()); + } + + // Use the most recent of published and updated dates + $item->setDate(max($item->getPublishedDate(), $item->getUpdatedDate())); + } + + /** + * Get Item Post Processor instance + * + * @access public + * @return ItemPostProcessor + */ + public function getItemPostProcessor() + { + return $this->itemPostProcessor; + } + + /** + * Get DateParser instance + * + * @access public + * @return DateParser + */ + public function getDateParser() + { + if ($this->dateParser === null) { + $this->dateParser = new DateParser($this->config); + } + + return $this->dateParser; + } + + /** + * Generate a unique id for an entry (hash all arguments). + * + * @return string + */ + public function generateId() + { + return hash($this->hash_algo, implode(func_get_args())); + } + + /** + * Return true if the given language is "Right to Left". + * + * @static + * @param string $language Language: fr-FR, en-US + * @return bool + */ + public static function isLanguageRTL($language) + { + $language = strtolower($language); + + $rtl_languages = array( + 'ar', // Arabic (ar-**) + 'fa', // Farsi (fa-**) + 'ur', // Urdu (ur-**) + 'ps', // Pashtu (ps-**) + 'syr', // Syriac (syr-**) + 'dv', // Divehi (dv-**) + 'he', // Hebrew (he-**) + 'yi', // Yiddish (yi-**) + ); + + foreach ($rtl_languages as $prefix) { + if (strpos($language, $prefix) === 0) { + return true; + } + } + + return false; + } + + /** + * Set Hash algorithm used for id generation. + * + * @param string $algo Algorithm name + * @return \PicoFeed\Parser\Parser + */ + public function setHashAlgo($algo) + { + $this->hash_algo = $algo ?: $this->hash_algo; + return $this; + } + + /** + * Set config object. + * + * @param \PicoFeed\Config\Config $config Config instance + * @return \PicoFeed\Parser\Parser + */ + public function setConfig($config) + { + $this->config = $config; + $this->itemPostProcessor->setConfig($config); + return $this; + } + + /** + * Enable the content grabber. + * + * @return \PicoFeed\Parser\Parser + */ + public function disableContentFiltering() + { + $this->itemPostProcessor->unregister('PicoFeed\Processor\ContentFilterProcessor'); + return $this; + } + + /** + * Enable the content grabber. + * + * @param bool $needsRuleFile true if only pages with rule files should be + * scraped + * @param null|\Closure $scraperCallback Callback function that gets called for each + * scraper execution + * @return \PicoFeed\Parser\Parser + */ + public function enableContentGrabber($needsRuleFile = false, $scraperCallback = null) + { + $processor = new ScraperProcessor($this->config); + + if ($needsRuleFile) { + $processor->getScraper()->disableCandidateParser(); + } + + if ($scraperCallback !== null) { + $processor->setExecutionCallback($scraperCallback); + } + + $this->itemPostProcessor->register($processor); + return $this; + } + + /** + * Set ignored URLs for the content grabber. + * + * @param array $urls URLs + * @return \PicoFeed\Parser\Parser + */ + public function setGrabberIgnoreUrls(array $urls) + { + $this->itemPostProcessor->getProcessor('PicoFeed\Processor\ScraperProcessor')->ignoreUrls($urls); + return $this; + } + + /** + * Register all supported namespaces to be used within an xpath query. + * + * @param SimpleXMLElement $xml Feed xml + * @return SimpleXMLElement + */ + public function registerSupportedNamespaces(SimpleXMLElement $xml) + { + foreach ($this->namespaces as $prefix => $ns) { + $xml->registerXPathNamespace($prefix, $ns); + } + + return $xml; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/ParserException.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/ParserException.php new file mode 100644 index 0000000..b5fbb69 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/ParserException.php @@ -0,0 +1,15 @@ + 'http://purl.org/rss/1.0/', + 'dc' => 'http://purl.org/dc/elements/1.1/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'feedburner' => 'http://rssnamespace.org/feedburner/ext/1.0', + ); + + /** + * Get the path to the items XML tree. + * + * @param SimpleXMLElement $xml Feed xml + * @return SimpleXMLElement[] + */ + public function getItemsTree(SimpleXMLElement $xml) + { + return XmlParser::getXPathResult($xml, 'rss:item', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'item') + ?: $xml->item; + } + + /** + * Find the feed url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedUrl(SimpleXMLElement $xml, Feed $feed) + { + $feed->setFeedUrl(''); + } + + /** + * Find the site url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findSiteUrl(SimpleXMLElement $xml, Feed $feed) + { + $value = XmlParser::getXPathResult($xml, 'rss:channel/rss:link', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/link') + ?: $xml->channel->link; + + $feed->setSiteUrl(XmlParser::getValue($value)); + } + + /** + * Find the feed description. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDescription(SimpleXMLElement $xml, Feed $feed) + { + $description = XmlParser::getXPathResult($xml, 'rss:channel/rss:description', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/description') + ?: $xml->channel->description; + + $feed->setDescription(XmlParser::getValue($description)); + } + + /** + * Find the feed logo url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLogo(SimpleXMLElement $xml, Feed $feed) + { + $logo = XmlParser::getXPathResult($xml, 'rss:image/rss:url', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'image/url'); + + $feed->setLogo(XmlParser::getValue($logo)); + } + + /** + * Find the feed icon. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedIcon(SimpleXMLElement $xml, Feed $feed) + { + $feed->setIcon(''); + } + + /** + * Find the feed title. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedTitle(SimpleXMLElement $xml, Feed $feed) + { + $title = XmlParser::getXPathResult($xml, 'rss:channel/rss:title', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/title') + ?: $xml->channel->title; + + $feed->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $feed->getSiteUrl()); + } + + /** + * Find the feed language. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLanguage(SimpleXMLElement $xml, Feed $feed) + { + $language = XmlParser::getXPathResult($xml, 'rss:channel/dc:language', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/dc:language', $this->namespaces); + + $feed->setLanguage(XmlParser::getValue($language)); + } + + /** + * Find the feed id. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedId(SimpleXMLElement $xml, Feed $feed) + { + $feed->setId($feed->getFeedUrl() ?: $feed->getSiteUrl()); + } + + /** + * Find the feed date. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDate(SimpleXMLElement $xml, Feed $feed) + { + $date = XmlParser::getXPathResult($xml, 'rss:channel/dc:date', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/dc:date', $this->namespaces); + + $feed->setDate($this->getDateParser()->getDateTime(XmlParser::getValue($date))); + } + + /** + * Find the item published date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemPublishedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $date = XmlParser::getXPathResult($entry, 'dc:date', $this->namespaces); + + $item->setPublishedDate(!empty($date) ? $this->getDateParser()->getDateTime(XmlParser::getValue($date)) : null); + } + + /** + * Find the item updated date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemUpdatedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + if ($item->publishedDate === null) { + $this->findItemPublishedDate($entry, $item, $feed); + } + $item->setUpdatedDate($item->getPublishedDate()); // No updated date in RSS 1.0 specifications + } + + /** + * Find the item title. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemTitle(SimpleXMLElement $entry, Item $item) + { + $title = XmlParser::getXPathResult($entry, 'rss:title', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'title') + ?: $entry->title; + + $item->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $item->getUrl()); + } + + /** + * Find the item author. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthor(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + $author = XmlParser::getXPathResult($entry, 'dc:creator', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'rss:channel/dc:creator', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/dc:creator', $this->namespaces); + + $item->setAuthor(XmlParser::getValue($author)); + } + + /** + * Find the item author URL. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthorUrl(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + // There appears to be no support for author URL in the dc: terms + $item->setAuthorUrl(''); + } + + /** + * Find the item content. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemContent(SimpleXMLElement $entry, Item $item) + { + $content = XmlParser::getXPathResult($entry, 'content:encoded', $this->namespaces); + + if (XmlParser::getValue($content) === '') { + $content = XmlParser::getXPathResult($entry, 'rss:description', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'description') + ?: $entry->description; + } + + $item->setContent(XmlParser::getValue($content)); + } + + /** + * Find the item URL. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemUrl(SimpleXMLElement $entry, Item $item) + { + $link = XmlParser::getXPathResult($entry, 'feedburner:origLink', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'rss:link', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'link') + ?: $entry->link; + + $item->setUrl(XmlParser::getValue($link)); + } + + /** + * Genereate the item id. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemId(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $item->setId($this->generateId( + $item->getTitle(), $item->getUrl(), $item->getContent() + )); + } + + /** + * Find the item enclosure. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemEnclosure(SimpleXMLElement $entry, Item $item, Feed $feed) + { + } + + /** + * Find the item language. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $language = XmlParser::getXPathResult($entry, 'dc:language', $this->namespaces); + + $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); + } + + /** + * Find the item categories. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param Feed $feed Feed object + */ + public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $categories = XmlParser::getXPathResult($entry, 'dc:subject', $this->namespaces); + $item->setCategoriesFromXml($categories); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss20.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss20.php new file mode 100644 index 0000000..da9c0d5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss20.php @@ -0,0 +1,330 @@ + 'http://purl.org/dc/elements/1.1/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'feedburner' => 'http://rssnamespace.org/feedburner/ext/1.0', + 'atom' => 'http://www.w3.org/2005/Atom', + ); + + /** + * Get the path to the items XML tree. + * + * @param SimpleXMLElement $xml Feed xml + * @return SimpleXMLElement[] + */ + public function getItemsTree(SimpleXMLElement $xml) + { + return XmlParser::getXPathResult($xml, 'channel/item'); + } + + /** + * Find the feed url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedUrl(SimpleXMLElement $xml, Feed $feed) + { + $feed->setFeedUrl(''); + } + + /** + * Find the site url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findSiteUrl(SimpleXMLElement $xml, Feed $feed) + { + $value = XmlParser::getXPathResult($xml, 'channel/link'); + $feed->setSiteUrl(XmlParser::getValue($value)); + } + + /** + * Find the feed description. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDescription(SimpleXMLElement $xml, Feed $feed) + { + $value = XmlParser::getXPathResult($xml, 'channel/description'); + $feed->setDescription(XmlParser::getValue($value)); + } + + /** + * Find the feed logo url. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLogo(SimpleXMLElement $xml, Feed $feed) + { + $value = XmlParser::getXPathResult($xml, 'channel/image/url'); + $feed->setLogo(XmlParser::getValue($value)); + } + + /** + * Find the feed icon. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedIcon(SimpleXMLElement $xml, Feed $feed) + { + $feed->setIcon(''); + } + + /** + * Find the feed title. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedTitle(SimpleXMLElement $xml, Feed $feed) + { + $title = XmlParser::getXPathResult($xml, 'channel/title'); + $feed->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $feed->getSiteUrl()); + } + + /** + * Find the feed language. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedLanguage(SimpleXMLElement $xml, Feed $feed) + { + $value = XmlParser::getXPathResult($xml, 'channel/language'); + $feed->setLanguage(XmlParser::getValue($value)); + } + + /** + * Find the feed id. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedId(SimpleXMLElement $xml, Feed $feed) + { + $feed->setId($feed->getFeedUrl() ?: $feed->getSiteUrl()); + } + + /** + * Find the feed date. + * + * @param SimpleXMLElement $xml Feed xml + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findFeedDate(SimpleXMLElement $xml, Feed $feed) + { + $publish_date = XmlParser::getXPathResult($xml, 'channel/pubDate'); + $update_date = XmlParser::getXPathResult($xml, 'channel/lastBuildDate'); + + $published = !empty($publish_date) ? $this->getDateParser()->getDateTime(XmlParser::getValue($publish_date)) : null; + $updated = !empty($update_date) ? $this->getDateParser()->getDateTime(XmlParser::getValue($update_date)) : null; + + if ($published === null && $updated === null) { + $feed->setDate($this->getDateParser()->getCurrentDateTime()); // We use the current date if there is no date for the feed + } elseif ($published !== null && $updated !== null) { + $feed->setDate(max($published, $updated)); // We use the most recent date between published and updated + } else { + $feed->setDate($updated ?: $published); + } + } + + /** + * Find the item published date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemPublishedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $date = XmlParser::getXPathResult($entry, 'pubDate'); + + $item->setPublishedDate(!empty($date) ? $this->getDateParser()->getDateTime(XmlParser::getValue($date)) : null); + } + + /** + * Find the item updated date. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemUpdatedDate(SimpleXMLElement $entry, Item $item, Feed $feed) + { + if ($item->publishedDate === null) { + $this->findItemPublishedDate($entry, $item, $feed); + } + $item->setUpdatedDate($item->getPublishedDate()); // No updated date in RSS 2.0 specifications + } + + /** + * Find the item title. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemTitle(SimpleXMLElement $entry, Item $item) + { + $value = XmlParser::getXPathResult($entry, 'title'); + $item->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($value)) ?: $item->getUrl()); + } + + /** + * Find the item author. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthor(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + $value = XmlParser::getXPathResult($entry, 'dc:creator', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'author') + ?: XmlParser::getXPathResult($xml, 'channel/dc:creator', $this->namespaces) + ?: XmlParser::getXPathResult($xml, 'channel/managingEditor'); + + $item->setAuthor(XmlParser::getValue($value)); + } + + /** + * Find the item author URL. + * + * @param SimpleXMLElement $xml Feed + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemAuthorUrl(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item) + { + // There appears to be no support for author URL in the dc: terms or author element + $item->setAuthorUrl(''); + } + + /** + * Find the item content. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemContent(SimpleXMLElement $entry, Item $item) + { + $content = XmlParser::getXPathResult($entry, 'content:encoded', $this->namespaces); + + if (XmlParser::getValue($content) === '') { + $content = XmlParser::getXPathResult($entry, 'description'); + } + + $item->setContent(XmlParser::getValue($content)); + } + + /** + * Find the item URL. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + */ + public function findItemUrl(SimpleXMLElement $entry, Item $item) + { + $link = XmlParser::getXPathResult($entry, 'feedburner:origLink', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'link') + ?: XmlParser::getXPathResult($entry, 'atom:link/@href', $this->namespaces); + + if (!empty($link)) { + $item->setUrl(XmlParser::getValue($link)); + } else { + $link = XmlParser::getXPathResult($entry, 'guid'); + $link = XmlParser::getValue($link); + + if (filter_var($link, FILTER_VALIDATE_URL) !== false) { + $item->setUrl($link); + } + } + } + + /** + * Genereate the item id. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemId(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $id = XmlParser::getValue(XmlParser::getXPathResult($entry, 'guid')); + + if ($id) { + $item->setId($this->generateId($id)); + } else { + $item->setId($this->generateId( + $item->getTitle(), $item->getUrl(), $item->getContent() + )); + } + } + + /** + * Find the item enclosure. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemEnclosure(SimpleXMLElement $entry, Item $item, Feed $feed) + { + if (isset($entry->enclosure)) { + $type = XmlParser::getXPathResult($entry, 'enclosure/@type'); + $url = XmlParser::getXPathResult($entry, 'feedburner:origEnclosureLink', $this->namespaces) + ?: XmlParser::getXPathResult($entry, 'enclosure/@url'); + + $item->setEnclosureUrl(Url::resolve(XmlParser::getValue($url), $feed->getSiteUrl())); + $item->setEnclosureType(XmlParser::getValue($type)); + } + } + + /** + * Find the item language. + * + * @param SimpleXMLElement $entry Feed item + * @param \PicoFeed\Parser\Item $item Item object + * @param \PicoFeed\Parser\Feed $feed Feed object + */ + public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $language = XmlParser::getXPathResult($entry, 'dc:language', $this->namespaces); + $item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage()); + } + + /** + * Find the item categories. + * + * @param SimpleXMLElement $entry Feed item + * @param Item $item Item object + * @param Feed $feed Feed object + */ + public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed) + { + $categories = XmlParser::getXPathResult($entry, 'category'); + $item->setCategoriesFromXml($categories); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss91.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss91.php new file mode 100644 index 0000000..058fca1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Parser/Rss91.php @@ -0,0 +1,13 @@ +childNodes->length === 0) { + return false; + } + + return $dom; + } + + /** + * Small wrapper around Laminas Xml to turn their exceptions into PicoFeed exceptions + * + * @static + * @access private + * @param string $input + * @param DOMDocument $dom + * @throws XmlEntityException + * @return SimpleXMLElement|DomDocument|boolean + */ + private static function scan($input, $dom = null) + { + try { + return Security::scan($input, $dom); + } catch(RuntimeException $e) { + throw new XmlEntityException($e->getMessage()); + } + } + + /** + * Load HTML document by using a DomDocument instance or return false on failure. + * + * @static + * @access public + * @param string $input XML content + * @return DOMDocument + */ + public static function getHtmlDocument($input) + { + $dom = new DomDocument(); + + if (empty($input)) { + return $dom; + } + + libxml_use_internal_errors(true); + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $dom->loadHTML($input, LIBXML_NONET); + } else { + $dom->loadHTML($input); + } + + self::$errors = []; + foreach (libxml_get_errors() as $error) { + self::$errors[] = sprintf('XML error: %s (Line: %d - Column: %d - Code: %d)', + $error->message, + $error->line, + $error->column, + $error->code + ); + } + + libxml_use_internal_errors(false); + + return $dom; + } + + /** + * Convert a HTML document to XML. + * + * @static + * @access public + * @param string $html HTML document + * @return string + */ + public static function htmlToXml($html) + { + $dom = self::getHtmlDocument(''.$html); + return $dom->saveXML($dom->getElementsByTagName('body')->item(0)); + } + + /** + * Get XML parser errors. + * + * @static + * @access public + * @return string + */ + public static function getErrors() + { + return implode(', ', self::$errors); + } + + /** + * Get the encoding from a xml tag. + * + * @static + * @access public + * @param string $data Input data + * @return string + */ + public static function getEncodingFromXmlTag($data) + { + $encoding = ''; + + if (strpos($data, '')); + $data = str_replace("'", '"', $data); + + $p1 = strpos($data, 'encoding='); + $p2 = strpos($data, '"', $p1 + 10); + + if ($p1 !== false && $p2 !== false) { + $encoding = substr($data, $p1 + 10, $p2 - $p1 - 10); + $encoding = strtolower($encoding); + } + } + + return $encoding; + } + + /** + * Get the charset from a meta tag. + * + * @static + * @access public + * @param string $data Input data + * @return string + */ + public static function getEncodingFromMetaTag($data) + { + $encoding = ''; + + if (preg_match('/;]+)/i', $data, $match) === 1) { + $encoding = strtolower($match[1]); + } + + return $encoding; + } + + /** + * Rewrite XPath query to use namespace-uri and local-name derived from prefix. + * + * @static + * @access public + * @param string $query XPath query + * @param array $ns Prefix to namespace URI mapping + * @return string + */ + public static function replaceXPathPrefixWithNamespaceURI($query, array $ns) + { + return preg_replace_callback('/([A-Z0-9]+):([A-Z0-9]+)/iu', function ($matches) use ($ns) { + // don't try to map the special prefix XML + if (strtolower($matches[1]) === 'xml') { + return $matches[0]; + } + + return '*[namespace-uri()="'.$ns[$matches[1]].'" and local-name()="'.$matches[2].'"]'; + }, + $query); + } + + /** + * Get the result elements of a XPath query. + * + * @static + * @access public + * @param SimpleXMLElement $xml XML element + * @param string $query XPath query + * @param array $ns Prefix to namespace URI mapping + * @return SimpleXMLElement[] + */ + public static function getXPathResult(SimpleXMLElement $xml, $query, array $ns = array()) + { + if (!empty($ns)) { + $query = static::replaceXPathPrefixWithNamespaceURI($query, $ns); + } + + return $xml->xpath($query); + } + + /** + * Get the first Xpath result or SimpleXMLElement value + * + * @static + * @access public + * @param mixed $value + * @return string + */ + public static function getValue($value) + { + $result = ''; + + if (is_array($value) && count($value) > 0) { + $result = (string) $value[0]; + } elseif (is_a($value, 'SimpleXMLElement')) { + return $result = (string) $value; + } + + return trim($result); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/PicoFeedException.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/PicoFeedException.php new file mode 100644 index 0000000..2de9e4b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/PicoFeedException.php @@ -0,0 +1,14 @@ +config->getContentFiltering(true)) { + $filter = Filter::html($item->getContent(), $feed->getSiteUrl()); + $filter->setConfig($this->config); + $item->setContent($filter->execute()); + } else { + Logger::setMessage(get_called_class().': Content filtering disabled'); + } + + return false; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ContentGeneratorProcessor.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ContentGeneratorProcessor.php new file mode 100644 index 0000000..49adf9c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ContentGeneratorProcessor.php @@ -0,0 +1,49 @@ +generators as $generator) { + $className = '\PicoFeed\Generator\\'.ucfirst($generator).'ContentGenerator'; + $object = new $className($this->config); + + if ($object->execute($item)) { + return true; + } + } + + return false; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemPostProcessor.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemPostProcessor.php new file mode 100644 index 0000000..7b092b5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemPostProcessor.php @@ -0,0 +1,106 @@ +processors as $processor) { + if ($processor->execute($feed, $item)) { + return true; + } + } + + return false; + } + + /** + * Register a new Item post-processor + * + * @access public + * @param ItemProcessorInterface $processor + * @return ItemPostProcessor + */ + public function register(ItemProcessorInterface $processor) + { + $this->processors[get_class($processor)] = $processor; + return $this; + } + + /** + * Remove Processor instance + * + * @access public + * @param string $class + * @return ItemPostProcessor + */ + public function unregister($class) + { + if (isset($this->processors[$class])) { + unset($this->processors[$class]); + } + + return $this; + } + + /** + * Checks whether a specific processor is registered or not + * + * @access public + * @param string $class + * @return bool + */ + public function hasProcessor($class) + { + return isset($this->processors[$class]); + } + + /** + * Get Processor instance + * + * @access public + * @param string $class + * @return ItemProcessorInterface|null + */ + public function getProcessor($class) + { + return isset($this->processors[$class]) ? $this->processors[$class] : null; + } + + public function setConfig(Config $config) + { + foreach ($this->processors as $processor) { + $processor->setConfig($config); + } + + return false; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemProcessorInterface.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemProcessorInterface.php new file mode 100644 index 0000000..5d53226 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Processor/ItemProcessorInterface.php @@ -0,0 +1,25 @@ +executionCallback = $executionCallback; + return $this; + } + + /** + * Execute Item Processor + * + * @access public + * @param Feed $feed + * @param Item $item + * @return bool + */ + public function execute(Feed $feed, Item $item) + { + if (!in_array($item->getUrl(), $this->ignoredUrls)) { + $scraper = $this->getScraper(); + $scraper->setUrl($item->getUrl()); + $scraper->execute(); + + if ($this->executionCallback && is_callable($this->executionCallback)) { + call_user_func($this->executionCallback, $feed, $item, $scraper); + } + + if ($scraper->hasRelevantContent()) { + $item->setContent($scraper->getFilteredContent()); + } + } + + return false; + } + + /** + * Ignore list of URLs + * + * @access public + * @param array $urls + * @return $this + */ + public function ignoreUrls(array $urls) + { + $this->ignoredUrls = $urls; + return $this; + } + + /** + * Returns Scraper instance + * + * @access public + * @return Scraper + */ + public function getScraper() + { + if ($this->scraper === null) { + $this->scraper = new Scraper($this->config); + } + + return $this->scraper; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Favicon.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Favicon.php new file mode 100644 index 0000000..d4ca07d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Favicon.php @@ -0,0 +1,186 @@ +content; + } + + /** + * Get the icon file type (available only after the download). + * + * @return string + */ + public function getType() + { + foreach ($this->types as $type) { + if (strpos($this->content_type, $type) === 0) { + return $type; + } + } + + return 'image/x-icon'; + } + + /** + * Get data URI (http://en.wikipedia.org/wiki/Data_URI_scheme). + * + * @return string + */ + public function getDataUri() + { + if (empty($this->content)) { + return ''; + } + + return sprintf( + 'data:%s;base64,%s', + $this->getType(), + base64_encode($this->content) + ); + } + + /** + * Download and check if a resource exists. + * + * @param string $url URL + * @return \PicoFeed\Client\Client Client instance + */ + public function download($url) + { + $client = Client::getInstance(); + $client->setConfig($this->config); + + Logger::setMessage(get_called_class().' Download => '.$url); + + try { + $client->execute($url); + } catch (ClientException $e) { + Logger::setMessage(get_called_class().' Download Failed => '.$e->getMessage()); + } + + return $client; + } + + /** + * Check if a remote file exists. + * + * @param string $url URL + * @return bool + */ + public function exists($url) + { + return $this->download($url)->getContent() !== ''; + } + + /** + * Get the icon link for a website. + * + * @param string $website_link URL + * @param string $favicon_link optional URL + * @return string + */ + public function find($website_link, $favicon_link = '') + { + $website = new Url($website_link); + + if ($favicon_link !== '') { + $icons = array($favicon_link); + } else { + $icons = $this->extract($this->download($website->getBaseUrl('/'))->getContent()); + $icons[] = $website->getBaseUrl('/favicon.ico'); + } + + foreach ($icons as $icon_link) { + $icon_link = Url::resolve($icon_link, $website); + $resource = $this->download($icon_link); + $this->content = $resource->getContent(); + $this->content_type = $resource->getContentType(); + + if ($this->content !== '') { + return $icon_link; + } elseif ($favicon_link !== '') { + return $this->find($website_link); + } + } + + return ''; + } + + /** + * Extract the icon links from the HTML. + * + * @param string $html HTML + * @return array + */ + public function extract($html) + { + $icons = array(); + + if (empty($html)) { + return $icons; + } + + $dom = XmlParser::getHtmlDocument($html); + + $xpath = new DOMXpath($dom); + $elements = $xpath->query('//link[@rel="icon" or @rel="shortcut icon" or @rel="Shortcut Icon" or @rel="icon shortcut"]'); + + for ($i = 0; $i < $elements->length; ++$i) { + $icons[] = $elements->item($i)->getAttribute('href'); + } + + return $icons; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Reader.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Reader.php new file mode 100644 index 0000000..596b02d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/Reader.php @@ -0,0 +1,189 @@ + '//feed', + 'Rss20' => '//rss[@version="2.0"]', + 'Rss92' => '//rss[@version="0.92"]', + 'Rss91' => '//rss[@version="0.91"]', + 'Rss10' => '//rdf', + ); + + /** + * Download a feed (no discovery). + * + * @param string $url Feed url + * @param string $last_modified Last modified HTTP header + * @param string $etag Etag HTTP header + * @param string $username HTTP basic auth username + * @param string $password HTTP basic auth password + * + * @return \PicoFeed\Client\Client + */ + public function download($url, $last_modified = '', $etag = '', $username = '', $password = '') + { + $url = $this->prependScheme($url); + + return Client::getInstance() + ->setConfig($this->config) + ->setLastModified($last_modified) + ->setEtag($etag) + ->setUsername($username) + ->setPassword($password) + ->execute($url); + } + + /** + * Discover and download a feed. + * + * @param string $url Feed or website url + * @param string $last_modified Last modified HTTP header + * @param string $etag Etag HTTP header + * @param string $username HTTP basic auth username + * @param string $password HTTP basic auth password + * @return Client + * @throws SubscriptionNotFoundException + */ + public function discover($url, $last_modified = '', $etag = '', $username = '', $password = '') + { + $client = $this->download($url, $last_modified, $etag, $username, $password); + + // It's already a feed or the feed was not modified + if (!$client->isModified() || $this->detectFormat($client->getContent())) { + return $client; + } + + // Try to find a subscription + $links = $this->find($client->getUrl(), $client->getContent()); + + if (empty($links)) { + throw new SubscriptionNotFoundException('Unable to find a subscription'); + } + + return $this->download($links[0], $last_modified, $etag, $username, $password); + } + + /** + * Find feed urls inside a HTML document. + * + * @param string $url Website url + * @param string $html HTML content + * + * @return array List of feed links + */ + public function find($url, $html) + { + Logger::setMessage(get_called_class().': Try to discover subscriptions'); + + $dom = XmlParser::getHtmlDocument($html); + $xpath = new DOMXPath($dom); + $links = array(); + + $queries = array( + '//link[@type="application/rss+xml"]', + '//link[@type="application/atom+xml"]', + ); + + foreach ($queries as $query) { + $nodes = $xpath->query($query); + + foreach ($nodes as $node) { + $link = $node->getAttribute('href'); + + if (!empty($link)) { + $feedUrl = new Url($link); + $siteUrl = new Url($url); + + $links[] = $feedUrl->getAbsoluteUrl($feedUrl->isRelativeUrl() ? $siteUrl->getBaseUrl() : ''); + } + } + } + + Logger::setMessage(get_called_class().': '.implode(', ', $links)); + + return $links; + } + + /** + * Get a parser instance. + * + * @param string $url Site url + * @param string $content Feed content + * @param string $encoding HTTP encoding + * @return \PicoFeed\Parser\Parser + * @throws UnsupportedFeedFormatException + */ + public function getParser($url, $content, $encoding) + { + $format = $this->detectFormat($content); + + if (empty($format)) { + throw new UnsupportedFeedFormatException('Unable to detect feed format'); + } + + $className = '\PicoFeed\Parser\\'.$format; + + $parser = new $className($content, $encoding, $url); + $parser->setHashAlgo($this->config->getParserHashAlgo()); + $parser->setConfig($this->config); + + return $parser; + } + + /** + * Detect the feed format. + * + * @param string $content Feed content + * @return string + */ + public function detectFormat($content) + { + $dom = XmlParser::getHtmlDocument($content); + $xpath = new DOMXPath($dom); + + foreach ($this->formats as $parser_name => $query) { + $nodes = $xpath->query($query); + + if ($nodes->length === 1) { + return $parser_name; + } + } + + return ''; + } + + /** + * Add the prefix "http://" if the end-user just enter a domain name. + * + * @param string $url Url + * @return string + */ + public function prependScheme($url) + { + if (!preg_match('%^https?://%', $url)) { + $url = 'http://'.$url; + } + + return $url; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/ReaderException.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/ReaderException.php new file mode 100644 index 0000000..4f03dbe --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Reader/ReaderException.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://combat.blog.lemonde.fr/2013/08/31/teddy-riner-le-rookie-devenu-rambo/#xtor=RSS-3208', + 'body' => array( + '//div[@class="entry-content"]', + ), + 'strip' => array( + '//*[contains(@class, "fb-like") or contains(@class, "social")]' + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.blogs.nytimes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.blogs.nytimes.com.php new file mode 100644 index 0000000..ee641b0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.blogs.nytimes.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'title' => '//header/h1', + 'test_url' => 'http://bits.blogs.nytimes.com/2012/01/16/wikipedia-plans-to-go-dark-on-wednesday-to-protest-sopa/', + 'body' => array( + '//div[@class="postContent"]', + ), + 'strip' => array( + '//*[@class="shareToolsBox"]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.igen.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.igen.fr.php new file mode 100644 index 0000000..f2028f4 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.igen.fr.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.igen.fr/ailleurs/2014/05/nvidia-va-delaisser-les-smartphones-grand-public-86031', + 'body' => array( + '//div[contains(@class, "field-name-body")]' + ), + 'strip' => array( + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.nytimes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.nytimes.com.php new file mode 100644 index 0000000..8ff921c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.nytimes.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nytimes.com/2011/05/15/world/middleeast/15prince.html', + 'body' => array( + '//p[contains(@class, "story-content")] | //div[@class="image"]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.over-blog.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.over-blog.com.php new file mode 100644 index 0000000..cc5d83c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.over-blog.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://eliascarpe.over-blog.com/2015/12/re-upload-projets-d-avenir.html', + 'body' => array( + '//div[contains(concat(" ", normalize-space(@class), " "), " ob-section ")]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.phoronix.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.phoronix.com.php new file mode 100644 index 0000000..66713f7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.phoronix.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.phoronix.com/scan.php?page=article&item=amazon_ec2_bare&num=1', + 'body' => array( + '//div[@class="content"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.slate.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.slate.com.php new file mode 100644 index 0000000..a795bca --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.slate.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.slate.com/articles/business/moneybox/2013/08/microsoft_ceo_steve_ballmer_retires_a_firsthand_account_of_the_company_s.html', + 'body' => array( + '//div[@class="sl-art-body"]', + ), + 'strip' => array( + '//*[contains(@class, "social") or contains(@class, "comments") or contains(@class, "sl-article-floatin-tools") or contains(@class, "sl-art-pag")]', + '//*[@id="mys_slate_logged_in"]', + '//*[@id="sl_article_tools_myslate_bottom"]', + '//*[@id="mys_myslate"]', + '//*[@class="sl-viral-container"]', + '//*[@class="sl-art-creds-cntr"]', + '//*[@class="sl-art-ad-midflex"]', + ) + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.theguardian.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.theguardian.com.php new file mode 100644 index 0000000..e0d6f3f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.theguardian.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.theguardian.com/sustainable-business/2015/feb/02/2015-hyper-transparency-global-business', + 'body' => array( + '//div[contains(@class, "content__main-column--article")]', + ), + 'strip' => array( + '//div[contains(@class, "meta-container")]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wikipedia.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wikipedia.org.php new file mode 100644 index 0000000..7b8f76e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wikipedia.org.php @@ -0,0 +1,29 @@ + array( + '%.*%' => array( + 'test_url' => 'https://en.wikipedia.org/wiki/Grace_Hopper', + 'body' => array( + '//div[@id="bodyContent"]', + ), + 'strip' => array( + "//div[@id='toc']", + "//div[@id='catlinks']", + "//div[@id='jump-to-nav']", + "//div[@class='thumbcaption']//div[@class='magnify']", + "//table[@class='navbox']", + "//table[contains(@class, 'infobox')]", + "//div[@class='dablink']", + "//div[@id='contentSub']", + "//div[@id='siteSub']", + "//table[@id='persondata']", + "//table[contains(@class, 'metadata')]", + "//*[contains(@class, 'noprint')]", + "//*[contains(@class, 'printfooter')]", + "//*[contains(@class, 'editsection')]", + "//*[contains(@class, 'error')]", + "//span[@title='pronunciation:']", + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wired.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wired.com.php new file mode 100644 index 0000000..952b09a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wired.com.php @@ -0,0 +1,44 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.wired.com/gamelife/2013/09/ouya-free-the-games/', + 'body' => array( + '//div[@data-js="gallerySlides"]', + '//div[starts-with(@class,"post")]', + ), + 'strip' => array( + '//h1', + '//nav', + '//button', + '//figure[starts-with(@class,"rad-slide")]', + '//figure[starts-with(@class,"end-slate")]', + '//div[contains(@class,"mobile-")]', + '//div[starts-with(@class,"mob-gallery-launcher")]', + '//div[contains(@id,"mobile-")]', + '//span[contains(@class,"slide-count")]', + '//div[contains(@class,"show-ipad")]', + '//img[contains(@id,"-hero-bg")]', + '//div[@data-js="overlayWrap"]', + '//ul[contains(@class,"metadata")]', + '//div[@class="opening center"]', + '//p[contains(@class="byline-mob"]', + '//div[@id="o-gallery"]', + '//div[starts-with(@class,"sm-col")]', + '//div[contains(@class,"pad-b-huge")]', + '//a[contains(@class,"visually-hidden")]', + '//*[@class="social"]', + '//i', + '//div[@data-js="mobGalleryAd"]', + '//div[contains(@class,"footer")]', + '//div[contains(@data-js,"fader")]', + '//div[@id="sharing"]', + '//div[contains(@id,"related")]', + '//div[@id="most-pop"]', + '//ul[@id="article-tags"]', + '//style', + '//section[contains(@class,"footer")]' + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wsj.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wsj.com.php new file mode 100644 index 0000000..f6e6cc1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/.wsj.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://online.wsj.com/article/SB10001424127887324108204579023143974408428.html', + 'body' => array( + '//div[@class="articlePage"]', + ), + 'strip' => array( + '//*[@id="articleThumbnail_2"]', + '//*[@class="socialByline"]', + ) + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/01net.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/01net.com.php new file mode 100644 index 0000000..6d144f0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/01net.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.01net.com/editorial/624550/twitter-rachete-madbits-un-specialiste-francais-de-lanalyse-dimages/', + 'body' => array( + '//div[@class="article_ventre_box"]', + ), + 'strip' => array( + '//link', + '//*[contains(@class, "article_navigation")]', + '//h1', + '//*[contains(@class, "article_toolbarMain")]', + '//*[contains(@class, "article_imagehaute_box")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/24.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/24.hu.php new file mode 100644 index 0000000..6c269db --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/24.hu.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://24.hu/belfold/2017/10/20/millios-lehuzasok-miatt-razziaztak-egy-budapesti-barban-videoval/', + 'body' => array( + '//div[@class="post-title-wrapper"]/h1', + '//div[@class="post-header-wrapper has-img"]/img', + '//div[@class="post-body"]' + + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/444.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/444.hu.php new file mode 100644 index 0000000..e65bad1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/444.hu.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'https://444.hu/2017/10/20/tulszamlazo-helyen-utottek-rajta-budapest-belvarosaban', + 'body' => array( + '//div[@id="headline"]/h1', + '//article' + ), + 'strip' => array( + '//article/footer', + '//article/div[@class="jeti-roadblock ad"]', + '//figcaption', + '//noscript', + '//ul[@class="widget-stream-items"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/888.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/888.hu.php new file mode 100644 index 0000000..68cfe05 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/888.hu.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'https://888.hu/article-a-budapesti-szocik-nem-szeretik-a-videki-szocikat', + 'body' => array( + '//div[@id="cikkholder"]/h1', + '//div[@class="maincontent8"]' + ), + 'strip' => array( + '//div[@class="AdW"]', + '//h1[@class="olvastadmar"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/abstrusegoose.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/abstrusegoose.com.php new file mode 100644 index 0000000..752d041 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/abstrusegoose.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%alt="(.+)" title="(.+)" */>%' => '/>
$1
$2', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/achgut.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/achgut.com.php new file mode 100644 index 0000000..1e61fe6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/achgut.com.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.achgut.com/artikel/deutscher_herbst_wg_reichsstrasse_106', + 'body' => array( + '//div[@class="headerpict_half"]/div/img', + '//div[@class="beitrag"]/div[@class="teaser_blog_text"]' + ), + 'strip' => array( + '//div[@class="footer_blog_text"]', + '//div[@class="beitrag"]/div[@class="teaser_blog_text"]/h2[1]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/adventuregamers.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/adventuregamers.com.php new file mode 100644 index 0000000..98d384e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/adventuregamers.com.php @@ -0,0 +1,23 @@ + array( + '%^/news.*%' => array( + 'test_url' => 'http://www.adventuregamers.com/news/view/31079', + 'body' => array( + '//div[@class="bodytext"]', + ) + ), + '%^/videos.*%' => array( + 'test_url' => 'http://www.adventuregamers.com/videos/view/31056', + 'body' => array( + '//iframe', + ) + ), + '%^/articles.*%' => array( + 'test_url' => 'http://www.adventuregamers.com/articles/view/31049', + 'body' => array( + '//div[@class="cleft"]', + ) + ) + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/alainonline.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/alainonline.net.php new file mode 100644 index 0000000..f440b23 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/alainonline.net.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.alainonline.net/news_details.php?lang=arabic&sid=18907', + 'body' => array( + '//div[@class="news_details"]', + ), + 'strip' => array( + '//div[@class="news_details"]/div/div[last()]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/aljazeera.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/aljazeera.com.php new file mode 100644 index 0000000..c02eb21 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/aljazeera.com.php @@ -0,0 +1,25 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.aljazeera.com/news/2015/09/xi-jinping-seattle-china-150922230118373.html', + 'body' => array( + '//article[@id="main-story"]', + ), + 'strip' => array( + '//script', + '//header', + '//ul', + '//section[contains(@class,"profile")]', + '//a[@target="_self"]', + '//div[contains(@id,"_2")]', + '//div[contains(@id,"_3")]', + '//img[@class="viewMode"]', + '//table[contains(@class,"in-article-item")]', + '//div[@data-embed-type="Brightcove"]', + '//div[@class="QuoteContainer"]', + '//div[@class="BottomByLine"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allafrica.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allafrica.com.php new file mode 100644 index 0000000..e8a506d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allafrica.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.aljazeera.com/news/2015/09/xi-jinping-seattle-china-150922230118373.html', + 'body' => array( + '//div[@class="story-body"]', + ), + 'strip' => array( + '//p[@class="kindofstory"]', + '//cite[@class="byline"]', + '//div[@class="useful-top"]', + '//div[contains(@class,"related-topics")]', + '//links', + '//sharebar', + '//related-topics', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allgemeine-zeitung.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allgemeine-zeitung.de.php new file mode 100644 index 0000000..8ede99b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/allgemeine-zeitung.de.php @@ -0,0 +1,23 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.allgemeine-zeitung.de/lokales/polizei/mainz-gonsenheim-unbekannte-rauben-esso-tankstelle-in-kurt-schumacher-strasse-aus_14913147.htm', + 'body' => array( + '//div[contains(@class, "article")][1]', + ), + 'strip' => array( + '//read/h1', + '//*[@id="t-map"]', + '//*[contains(@class, "modules")]', + '//*[contains(@class, "adsense")]', + '//*[contains(@class, "linkbox")]', + '//*[contains(@class, "info")]', + '//*[@class="skip"]', + '//*[@class="funcs"]', + '//span[@class="nd address"]', + '//a[contains(@href, "abo-und-services")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/amazingsuperpowers.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/amazingsuperpowers.com.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/amazingsuperpowers.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/anythingcomic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/anythingcomic.com.php new file mode 100644 index 0000000..51247f7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/anythingcomic.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'body' => array( + '//img[@id="comic_image"]', + '//div[@class="comment-wrapper"][position()=1]', + ), + 'strip' => array(), + 'test_url' => 'http://www.anythingcomic.com/comics/2108929/stress-free/', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ap.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ap.org.php new file mode 100644 index 0000000..5bb2bb6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ap.org.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://hosted.ap.org/dynamic/stories/A/AS_CHINA_GAO_ZHISHENG?SITE=AP&SECTION=HOME&TEMPLATE=DEFAULT', + 'body' => array( + '//img[@class="ap-smallphoto-img"]', + '//span[@class="entry-content"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/areadvd.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/areadvd.de.php new file mode 100644 index 0000000..fc56922 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/areadvd.de.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.areadvd.de/news/daily-deals-angebote-bei-lautsprecher-teufel-3/', + 'body' => array('//div[contains(@class,"entry")]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/arstechnica.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/arstechnica.com.php new file mode 100644 index 0000000..55e01ce --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/arstechnica.com.php @@ -0,0 +1,25 @@ + array( + '%.*%' => array( + 'test_url' => 'http://arstechnica.com/tech-policy/2015/09/judge-warners-2m-happy-birthday-copyright-is-bogus/', + 'body' => array( + '//article', + ), + 'strip' => array( + '//h4[@class="post-upperdek"]', + '//h1', + '//ul[@class="lSPager lSGallery"]', + '//div[@class="lSAction"]', + '//section[@class="post-meta"]', + '//figcaption', + '//aside', + '//div[@class="gallery-image-credit"]', + '//section[@class="article-author"]', + '//*[contains(@id,"social-")]', + '//div[contains(@id,"footer")]', + ), + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/atv.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/atv.hu.php new file mode 100644 index 0000000..170ec4d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/atv.hu.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.atv.hu/belfold/20171020-tobb-millio-forintot-csalt-ki-egy-idos-ferfitol-a-budapesti-no', + 'body' => array( + '//article' + ), + 'strip' => array( + '//span[@class="date"]', + '//div[@class="fb-like db_iframe_widget"]', + '//div[@class="ad-wrapper dashed-border"]', + '//div[@class="footer-meta-wrapper"]', + '//div[@class="image-wrapper "]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/awkwardzombie.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/awkwardzombie.com.php new file mode 100644 index 0000000..5ab7051 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/awkwardzombie.com.php @@ -0,0 +1,10 @@ + array( + '%/index.php.*comic=.*%' => array( + 'test_url' => 'http://www.awkwardzombie.com/index.php?comic=041315', + 'body' => array('//*[@id="comic"]/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/backchannel.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/backchannel.com.php new file mode 100644 index 0000000..bc5932a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/backchannel.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'https://medium.com/lessons-learned/917b8b63ae3e', + 'body' => array( + '//div[contains(@class,"section-inner")]', + ), + 'strip' => array( + '//div[contains(@class,"metabar")]', + '//img[contains(@class,"thumbnail")]', + '//h1', + '//blockquote', + '//p[contains(@class,"graf-after--h4")]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bangkokpost.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bangkokpost.com.php new file mode 100644 index 0000000..165515b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bangkokpost.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bangkokpost.com/news/politics/704204/new-us-ambassador-arrives-in-bangkok', + 'body' => array( + '//article/div[@class="articleContents"]', + ), + 'strip' => array( + '//h2', + '//h4', + '//div[@class="text-size"]', + '//div[@class="relate-story"]', + '//div[@class="text-ads"]', + '//ul', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bauerwilli.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bauerwilli.com.php new file mode 100644 index 0000000..b191a6e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bauerwilli.com.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bauerwilli.com/intuitive-eating/', + 'body' => array( + '//div[@class="entry-thumbnail"]', + '//div[@class="entry-content"]', + ), + 'strip' => array( + '//div[@class="tptn_counter"]', + '//div[contains(@class, "sharedaddy")]' + ), + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bgr.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bgr.com.php new file mode 100644 index 0000000..7507a2f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bgr.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://bgr.com/2015/09/27/iphone-6s-waterproof-testing/', + 'body' => array( + '//img[contains(@class,"img")]', + '//div[@class="text-column"]', + ), + 'strip' => array( + '//strong', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigfootjustice.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigfootjustice.com.php new file mode 100644 index 0000000..d06ed12 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigfootjustice.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigpicture.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigpicture.ru.php new file mode 100644 index 0000000..55c4089 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bigpicture.ru.php @@ -0,0 +1,31 @@ + array( + '%.*%' => array( + 'test_url' => 'http://bigpicture.ru/?p=556658', + 'body' => array( + '//div[@class="article container"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//h1', + '//*[@class="wp-smiley"]', + '//div[@class="ipmd"]', + '//div[@class="tags"]', + '//div[@class="social-button"]', + '//div[@class="bottom-share"]', + '//div[@class="raccoonbox"]', + '//div[@class="yndadvert"]', + '//div[@class="we-recommend"]', + '//div[@class="relap-bigpicture_ru-wrapper"]', + '//div[@id="mmail"]', + '//div[@id="mobile-ads-cut"]', + '//div[@id="liquidstorm-alt-html"]', + '//div[contains(@class, "post-tags")]', + '//*[contains(text(),"Смотрите также")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bizjournals.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bizjournals.com.php new file mode 100644 index 0000000..d1cc3da --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bizjournals.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bizjournals.com/milwaukee/news/2015/09/30/bucks-will-hike-prices-on-best-seats-at-new-arena.html', + 'body' => array( + '//figure/div/a/img', + '//p[@class="content__segment"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/biztimes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/biztimes.com.php new file mode 100644 index 0000000..d21aa98 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/biztimes.com.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.biztimes.com/2017/02/10/settlement-would-revive-fowler-lake-condo-project-in-oconomowoc/', + 'body' => array( + '//h2/span[@class="subhead"]', + '//div[contains(@class,"article-content")]', + ), + 'strip' => array( + '//script', + '//div[contains(@class,"mobile-article-content")]', + '//div[contains(@class,"sharedaddy")]', + '//div[contains(@class,"author-details")]', + '//div[@class="row ad"]', + '//div[contains(@class,"relatedposts")]', + '//div[@class="col-lg-12"]', + '//div[contains(@class,"widget")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bleepingcomputer.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bleepingcomputer.com.php new file mode 100644 index 0000000..7b74060 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bleepingcomputer.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.bleepingcomputer.com/news/google/chromes-sandbox-feature-infringes-on-three-patents-so-google-must-now-pay-20m/', + 'body' => array( + '//div[@class="article_section"]', + ), + 'strip' => array( + '//*[@itemprop="headline"]', + '//div[@class="cz-news-story-title-section"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.fefe.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.fefe.de.php new file mode 100644 index 0000000..39c88ae --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.fefe.de.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://blog.fefe.de/?ts=ad706a73', + 'body' => array( + '/html/body/ul', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.mapillary.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.mapillary.com.php new file mode 100644 index 0000000..ce01651 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/blog.mapillary.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://blog.mapillary.com/update/2015/08/26/traffic-sign-updates.html', + 'body' => array( + '//div[contains(@class, "blog-post__content")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/brewers.mlb.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/brewers.mlb.com.php new file mode 100644 index 0000000..be406fa --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/brewers.mlb.com.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://m.brewers.mlb.com/news/article/161364798', + 'body' => array( + '//article[contains(@class,"article")]', + ), + 'strip' => array( + '//div[contains(@class,"ad-slot")]', + '//h1', + '//span[@class="timestamp"]', + '//div[contains(@class,"contributor-bottom")]', + '//div[contains(@class,"video")]', + '//ul[contains(@class,"social")]', + '//p[@class="tagline"]', + '//div[contains(@class,"social")]', + '//div[@class="button-wrap"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buenosairesherald.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buenosairesherald.com.php new file mode 100644 index 0000000..4e73e79 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buenosairesherald.com.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.buenosairesherald.com/article/199344/manzur-named-next-governor-of-tucum%C3%A1n', + 'body' => array( + '//div[@style="float:none"]', + ), + 'strip' => array( + '//div[contains(@class, "bz_alias_short_desc_container"]', + '//td[@id="bz_show_bug_column_1"]', + '//table[@id="attachment_table"]', + '//table[@class="bz_comment_table"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bunicomic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bunicomic.com.php new file mode 100644 index 0000000..ad83e43 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/bunicomic.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bunicomic.com/comic/buni-623/', + 'body' => array( + '//div[@class="comic-table"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buttersafe.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buttersafe.com.php new file mode 100644 index 0000000..1f313cd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/buttersafe.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://buttersafe.com/2015/04/21/the-incredible-flexible-man/', + 'body' => array( + '//div[@id="comic"]', + '//div[@class="post-comic"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cad-comic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cad-comic.com.php new file mode 100644 index 0000000..a631c97 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cad-comic.com.php @@ -0,0 +1,12 @@ + array( + '%/cad/.+%' => array( + 'test_url' => 'http://www.cad-comic.com/cad/20150417', + 'body' => array( + '//*[@id="content"]/img', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chaoslife.findchaos.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chaoslife.findchaos.com.php new file mode 100644 index 0000000..ea6191e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chaoslife.findchaos.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://chaoslife.findchaos.com/pets-in-the-wild', + 'body' => array('//div[@id="comic"]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chinafile.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chinafile.com.php new file mode 100644 index 0000000..450117b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/chinafile.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.chinafile.com/books/shanghai-faithful?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+chinafile%2FAll+%28ChinaFile%29', + 'body' => array( + '//div[contains(@class,"pane-featured-photo-panel-pane-1")]', + '//div[contains(@class,"video-above-fold")]', + '//div[@class="sc-media"]', + '//div[contains(@class,"field-name-body")]', + ), + 'strip' => array( + '//div[contains(@class,"cboxes")]', + '//div[contains(@class,"l-middle")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cicero.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cicero.de.php new file mode 100644 index 0000000..2cd1b70 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cicero.de.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'https://cicero.de/innenpolitik/plaene-der-eu-kommission-der-ganz-normale-terror', + 'body' => array( + '//p[@class="lead"]', + '//article/div[2]/div[contains(@class, "field--name-field-cc-image")]', + '//article/div[2]/div[contains(@class, "image-description")]', + '//div[@class="field field-name-field-cc-body"]', + ), + 'strip' => array( + '//*[contains(@class, "urban-ad-sign")]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cliquerefresh.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cliquerefresh.com.php new file mode 100644 index 0000000..9dcc7e5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cliquerefresh.com.php @@ -0,0 +1,10 @@ + array( + '%/comic.*%' => array( + 'test_url' => 'http://cliquerefresh.com/comic/078-stating-the-obvious/', + 'body' => array('//div[@class="comicImg"]/img | //div[@class="comicImg"]/a/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cnet.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cnet.com.php new file mode 100644 index 0000000..60767a5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cnet.com.php @@ -0,0 +1,37 @@ + array( + '%^/products.*%' => array( + 'test_url' => 'http://www.cnet.com/products/fibaro-flood-sensor/#ftag=CADf328eec', + 'body' => array( + '//li[contains(@class,"slide first"] || //figure[contains(@class,(promoFigure))]', + '//div[@class="quickInfo"]', + '//div[@class="col-6 ratings"]', + '//div[@id="editorReview"]', + ), + 'strip' => array( + '//script', + '//a[@class="clickToEnlarge"]', + '//div[@section="topSharebar"]', + '//div[contains(@class,"related")]', + '//div[contains(@class,"ad-")]', + '//div[@section="shortcodeGallery"]', + ), + ), + '%.*%' => array( + 'test_url' => 'http://cnet.com.feedsportal.com/c/34938/f/645093/s/4a340866/sc/28/l/0L0Scnet0N0Cnews0Cman0Eclaims0Eonline0Epsychic0Emade0Ehim0Ebuy0E10Emillion0Epowerball0Ewinning0Eticket0C0Tftag0FCAD590Aa51e/story01.htm', + 'body' => array( + '//p[@itemprop="description"]', + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//script', + '//a[@class="clickToEnlarge"]', + '//div[@section="topSharebar"]', + '//div[contains(@class,"related")]', + '//div[contains(@class,"ad-")]', + '//div[@section="shortcodeGallery"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/coinwelt.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/coinwelt.de.php new file mode 100644 index 0000000..28a8bba --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/coinwelt.de.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://coinwelt.de/2017/08/bitcache-kreierer-kim-dotcom-bietet-arbeitsplaetze-fuer-blockchain-goetter/', + 'body' => array( + '//div[@class="post-inner"]//div[@class="entry"]', + ), + 'strip' => array( + '//div[contains(@class, "shariff")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/consomac.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/consomac.fr.php new file mode 100644 index 0000000..9209f9c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/consomac.fr.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://consomac.fr/news-2430-l-iphone-6-toujours-un-secret-bien-garde.html', + 'body' => array( + '//div[contains(@id, "newscontent")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cowbirdsinlove.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cowbirdsinlove.com.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/cowbirdsinlove.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/crash.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/crash.net.php new file mode 100644 index 0000000..88cef14 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/crash.net.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.crash.net/motogp/news/885102/1/dovizioso-mugello-win-was-catalyst-for-title-challenge', + 'body' => array( + '//*[@id="block-system-main"]', + ), + 'strip' => array( + '//script', + '//style', + '//*[@class="social-bar"]', + '//*[@id="below-headline-image-ad"]', + '//*[@class="advert-"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/csmonitor.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/csmonitor.com.php new file mode 100644 index 0000000..481e4b0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/csmonitor.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.csmonitor.com/USA/Politics/2015/0925/John-Boehner-steps-down-Self-sacrificing-but-will-it-lead-to-better-government', + 'body' => array( + '//h2[@id="summary"]', + '//div[@class="flex-video youtube"]', + '//div[contains(@class,"eza-body")]', + ), + 'strip' => array( + '//span[@id="breadcrumb"]', + '//div[@id="byline-wrapper"]', + '//div[@class="injection"]', + '//*[contains(@class,"promo_link")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyjs.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyjs.com.php new file mode 100644 index 0000000..20eb1d7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyjs.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://dailyjs.com/2014/08/07/p5js/', + 'body' => array( + '//div[@id="post"]', + ), + 'strip' => array( + '//h2[@class="post"]', + '//div[@class="meta"]', + '//*[contains(@class, "addthis_toolbox")]', + '//*[contains(@class, "addthis_default_style")]', + '//*[@class="navigation small"]', + '//*[@id="related"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyreporter.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyreporter.com.php new file mode 100644 index 0000000..db3fc0e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailyreporter.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://dailyreporter.com/2016/01/09/us-supreme-court-case-could-weaken-government-workers-unions/', + 'body' => array( + '//div[contains(@class, "entry-content")]', + ), + 'strip' => array( + '//div[@class="dmcss_login_form"]', + '//*[contains(@class, "sharedaddy")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailytech.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailytech.com.php new file mode 100644 index 0000000..5d1df4a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dailytech.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.dailytech.com/Apples+First+Fixes+to+iOS+9+Land+w+iOS++901+Release/article37495.htm', + 'body' => array( + '//div[@class="NewsBodyImage"]', + '//span[@id="lblSummary"]', + '//span[@id="lblBody"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/degroupnews.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/degroupnews.com.php new file mode 100644 index 0000000..91f5c56 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/degroupnews.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.degroupnews.com/medias/vodsvod/amazon-concurrence-la-chromecast-de-google-avec-fire-tv-stick', + 'body' => array( + '//div[@class="contenu"]', + ), + 'strip' => array( + '//div[contains(@class, "a2a")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/derstandard.at.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/derstandard.at.php new file mode 100644 index 0000000..7e95a51 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/derstandard.at.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://derstandard.at/2000010267354/The-Witcher-3-Hohe-Hardware-Anforderungen-fuer-PC-Spieler?ref=rss', + 'body' => array( + '//div[@class="copytext"]', + '//ul[@id="media-list"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dilbert.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dilbert.com.php new file mode 100644 index 0000000..b8e9b3d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dilbert.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'body' => array( + '//img[@class="img-responsive img-comic"]', + ), + 'test_url' => 'http://dilbert.com/strip/2016-01-28', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/discovermagazine.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/discovermagazine.com.php new file mode 100644 index 0000000..ae0dfe7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/discovermagazine.com.php @@ -0,0 +1,26 @@ + array( + '%.*%' => array( + 'test_url' => 'http://blogs.discovermagazine.com/neuroskeptic/2017/01/25/publishers-jeffrey-beall/', + 'body' => array( + '//div[@class="contentWell"]', + ), + 'strip' => array( + '//h1', + '//div[@class="breadcrumbs"]', + '//div[@class="mobile"]', + '//div[@class="fromIssue"]', + '//div[contains(@class,"belowDeck")]', + '//div[@class="meta"]', + '//div[@class="shareIcons"]', + '//div[@class="categories"]', + '//div[@class="navigation"]', + '//div[@class="heading"]', + '//div[contains(@id,"-ad")]', + '//div[@class="relatedArticles"]', + '//div[@id="disqus_thread"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/distrowatch.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/distrowatch.com.php new file mode 100644 index 0000000..aefc8f8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/distrowatch.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://distrowatch.com/?newsid=08355', + 'body' => array( + '//td[@class="NewsText"][1]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dozodomo.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dozodomo.com.php new file mode 100644 index 0000000..e116695 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/dozodomo.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://dozodomo.com/bento/2014/03/04/lart-des-maki-de-takayo-kiyota/', + 'body' => array( + '//div[@class="joke"]', + '//div[@class="story-cover"]', + '//div[@class="story-content"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/drawingboardcomic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/drawingboardcomic.com.php new file mode 100644 index 0000000..cd30f2e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/drawingboardcomic.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'body' => array('//img[@id="comicimage"]'), + 'strip' => array(), + 'test_url' => 'http://drawingboardcomic.com/index.php?comic=208', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/e-w-e.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/e-w-e.ru.php new file mode 100644 index 0000000..8139cc9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/e-w-e.ru.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://e-w-e.ru/16-prekrasnyx-izobretenij-zhenshhin/', + 'body' => array( + '//div[contains(@class, "post_text")]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[@class="views_post"]', + '//*[@class="adman_mobile"]', + '//*[@class="adman_desctop"]', + '//*[contains(@rel, "nofollow")]', + '//*[contains(@class, "wp-smiley")]', + '//*[contains(text(),"Источник:")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/economist.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/economist.com.php new file mode 100644 index 0000000..522032f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/economist.com.php @@ -0,0 +1,25 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.economist.com/blogs/buttonwood/2017/02/mixed-signals?fsrc=rss', + 'body' => array( + '//article', + ), + 'strip' => array( + '//span[@class="blog-post__siblings-list-header "]', + '//h1', + '//aside', + '//div[@class="blog-post__asideable-wrapper"]', + '//div[@class="share_inline_header"]', + '//div[@id="column-right"]', + '//div[contains(@class,"blog-post__siblings-list-aside")]', + '//div[@class="video-player__wrapper"]', + '//div[@class="blog-post__bottom-panel"]', + '//div[contains(@class,"latest-updates-panel__container")]', + '//div[contains(@class,"blog-post__asideable-content")]', + '//div[@aria-label="Advertisement"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/encyclopedie.naheulbeuk.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/encyclopedie.naheulbeuk.com.php new file mode 100644 index 0000000..19bcbde --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/encyclopedie.naheulbeuk.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://encyclopedie.naheulbeuk.com/article.php3?id_article=352', + 'body' => array( + '//td//h1[@class="titre-texte"]', + '//td//div[@class="surtitre"]', + '//td//div[@class="texte"]', + ), + ) + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/endlessorigami.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/endlessorigami.com.php new file mode 100644 index 0000000..d06ed12 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/endlessorigami.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/engadget.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/engadget.com.php new file mode 100644 index 0000000..cf9e448 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/engadget.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.engadget.com/2015/04/20/dark-matter-discovery/?ncid=rss_truncated', + 'body' => array('//div[@id="page_body"]/div[@class="container@m-"]'), + 'strip' => array('//aside[@role="banner"]'), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/escapistmagazine.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/escapistmagazine.com.php new file mode 100644 index 0000000..e86b59c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/escapistmagazine.com.php @@ -0,0 +1,45 @@ + array( + '%/articles/view/comicsandcosplay/comics/critical-miss.*%' => array( + 'body' => array('//*[@class="body"]/span/img | //div[@class="folder_nav_links"]/following::p'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/critical-miss/13776-Critical-Miss-on-Framerates?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay/comics/namegame.*%' => array( + 'body' => array('//*[@class="body"]/span/p/img[@height != "120"]'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/namegame/9759-Leaving-the-Nest?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay/comics/stolen-pixels.*%' => array( + 'body' => array('//*[@class="body"]/span/p[2]/img'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/stolen-pixels/8866-Stolen-Pixels-258-Where-the-Boys-Are?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay/comics/bumhugparade.*%' => array( + 'body' => array('//*[@class="body"]/span/p[2]/img'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/bumhugparade/8262-Bumhug-Parade-13?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay.*/comics/escapistradiotheater%' => array( + 'body' => array('//*[@class="body"]/span/p[2]/img'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/escapistradiotheater/8265-The-Escapist-Radio-Theater-13?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay/comics/paused.*%' => array( + 'body' => array('//*[@class="body"]/span/p[2]/img | //*[@class="body"]/span/div/img'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/paused/8263-Paused-16?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/comicsandcosplay/comics/fraughtwithperil.*%' => array( + 'body' => array('//*[@class="body"]'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/fraughtwithperil/12166-The-Escapist-Presents-Escapist-Comics-Critical-Miss-B-lyeh-Fhlop?utm_source=rss&utm_medium=rss&utm_campaign=articles', + 'strip' => array(), + ), + '%/articles/view/video-games/columns/.*%' => array( + 'body' => array('//*[@id="article_content"]'), + 'test_url' => 'http://www.escapistmagazine.com/articles/view/video-games/columns/experienced-points/13971-What-50-Shades-and-Batman-Have-in-Common.2', + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/espn.go.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/espn.go.com.php new file mode 100644 index 0000000..76a20f7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/espn.go.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://espn.go.com/nfl/story/_/id/13388208/jason-whitlock-chip-kelly-controversy', + 'body' => array( + '//p', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/exocomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/exocomics.com.php new file mode 100644 index 0000000..5adc59f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/exocomics.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'body' => array('//a[@class="comic"]/img'), + 'strip' => array(), + 'test_url' => 'http://www.exocomics.com/379', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/explosm.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/explosm.net.php new file mode 100644 index 0000000..3fdf02c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/explosm.net.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://explosm.net/comics/3803/', + 'body' => array( + '//div[@id="comic-container"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/extrafabulouscomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/extrafabulouscomics.com.php new file mode 100644 index 0000000..12697cc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/extrafabulouscomics.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/factroom.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/factroom.ru.php new file mode 100644 index 0000000..a572061 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/factroom.ru.php @@ -0,0 +1,27 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.factroom.ru/life/20-facts-about-oil', + 'body' => array( + '//div[@class="post"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//h1', + '//div[@id="yandex_ad2"]', + '//*[@class="jp-relatedposts"]', + '//div[contains(@class, "likely-desktop")]', + '//div[contains(@class, "likely-mobile")]', + '//p[last()]', + '//div[contains(@class, "facebook")]', + '//div[contains(@class, "desktop-underpost-direct")]', + '//div[contains(@class, "source-box")]', + '//div[contains(@class, "under-likely-desktop")]', + '//div[contains(@class, "mobile-down-post")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcodesign.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcodesign.com.php new file mode 100644 index 0000000..74e70a8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcodesign.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.fastcodesign.com/3026548/exposure/peek-inside-the-worlds-forbidden-subway-tunnels', + 'body' => array( + '//article[contains(@class, "body prose")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcoexist.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcoexist.com.php new file mode 100644 index 0000000..6916f28 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcoexist.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.fastcoexist.com/3026114/take-a-seat-on-this-gates-funded-future-toilet-that-will-change-how-we-think-about-poop', + 'body' => array( + '//article[contains(@class, "body prose")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcompany.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcompany.com.php new file mode 100644 index 0000000..e0869a2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fastcompany.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.fastcompany.com/3026712/fast-feed/elon-musk-an-apple-tesla-merger-is-very-unlikely', + 'body' => array( + '//article[contains(@class, "body prose")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ffworld.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ffworld.com.php new file mode 100644 index 0000000..20a47b2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ffworld.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.ffworld.com/?rub=news&page=voir&id=2709', + 'body' => array( + '//div[@class="news_body"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/foreignpolicy.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/foreignpolicy.com.php new file mode 100644 index 0000000..3cbcddc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/foreignpolicy.com.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://foreignpolicy.com/2016/01/09/networking-giant-pulls-nsa-linked-code-exploited-by-hackers/', + 'body' => array( + '//article', + ), + 'strip' => array( + '//div[@id="post-category"]', + '//div[@id="desktop-right"]', + '//h1', + '//section[@class="article-meta"]', + '//div[@class="side-panel-wrapper"]', + '//*[contains(@class, "share-")]', + '//*[contains(@id, "taboola-")]', + '//div[@class="comments"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fossbytes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fossbytes.com.php new file mode 100644 index 0000000..6ce4725 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fossbytes.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://fossbytes.com/fbi-hacked-1000-computers-to-shut-down-largest-child-pornography-site-on-the-dark-web/', + 'body' => array( + '//div[@class="entry-inner"]', + ), + 'strip' => array( + '//*[@class="at-above-post addthis_default_style addthis_toolbox at-wordpress-hide"]', + '//*[@class="at-below-post addthis_default_style addthis_toolbox at-wordpress-hide"]', + '//*[@class="at-below-post-recommended addthis_default_style addthis_toolbox at-wordpress-hide"]', + '//*[@class="code-block code-block-12 ai-desktop"]', + '//*[@class="code-block code-block-13 ai-tablet-phone"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fototelegraf.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fototelegraf.ru.php new file mode 100644 index 0000000..ca2f85a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fototelegraf.ru.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://fototelegraf.ru/?p=348232', + 'body' => array( + '//div[@class="post-content"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//div[@class="imageButtonsBlock"]', + '//div[@class="adOnPostBtwImg"]', + '//div[contains(@class, "post-tags")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fowllanguagecomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fowllanguagecomics.com.php new file mode 100644 index 0000000..3f62f07 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/fowllanguagecomics.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'body' => array('//*[@id="comic"] | //*[@class="post-image"]'), + 'strip' => array(), + 'test_url' => 'http://www.fowllanguagecomics.com/comic/working-out/', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamechannel.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamechannel.hu.php new file mode 100644 index 0000000..8ab9c5c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamechannel.hu.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.gamechannel.hu/cikk/hirblock/a-legacy-of-kain-feltamasztasara-keszul-a-crystal-dynamics', + 'body' => array( + '//div[@class="post"]/div[@class="entry"]' + ), + 'strip' => array( + '//div[@class="valaszto"]', + '//center/blockquote' // as we can't grab iframe here + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamestar.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamestar.hu.php new file mode 100644 index 0000000..56a4c72 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gamestar.hu.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.gamestar.hu/hir/horizon-zero-dawn-the-frozen-wilds-vedjegy-239019.html', + 'body' => array( + '//article/header/h1', + '//div[@class="section section-2-3"]/div[@class="image"]/img', + '//article/p[@class="lead"]', + '//article/div[@class="content"]' + ), + 'strip' => array( + '//div[@class="ad ad-article-inside"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geek.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geek.com.php new file mode 100644 index 0000000..d9ccecc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geek.com.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.geek.com/news/the-11-best-ways-to-eat-eggs-1634076/', + 'body' => array( + '//div[@class="articleinfo"]/figure', + '//div[@class="articleinfo"]/article', + '//span[@class="by"]', + ), + 'strip' => array( + '//span[@class="red"]', + '//div[@class="on-target"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geektimes.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geektimes.ru.php new file mode 100644 index 0000000..1954138 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/geektimes.ru.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'https://geektimes.ru/post/289151/', + 'body' => array( + "//div[contains(concat(' ',normalize-space(@class),' '),' content ')]" + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gerbilwithajetpack.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gerbilwithajetpack.com.php new file mode 100644 index 0000000..44013b3 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gerbilwithajetpack.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'body' => array( + '//div[@id="comic-1"]', + '//div[@class="entry"]', + ), + 'test_url' => 'http://gerbilwithajetpack.com/passing-the-digital-buck/', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/giantitp.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/giantitp.com.php new file mode 100644 index 0000000..d9c3ae5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/giantitp.com.php @@ -0,0 +1,12 @@ + array( + '%/comics/oots.*%' => array( + 'test_url' => 'http://www.giantitp.com/comics/oots0989.html', + 'body' => array( + '//td[@align="center"]/img', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/github.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/github.com.php new file mode 100644 index 0000000..726634f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/github.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'https://github.com/audreyr/favicon-cheat-sheet', + 'body' => array( + '//article[contains(@class, "entry-content")]', + ), + 'strip' => array( + '//h1', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gocomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gocomics.com.php new file mode 100644 index 0000000..32960f0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gocomics.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.gocomics.com/pearlsbeforeswine/2015/05/30', + 'body' => array( + '//div[1]/p[1]/a[1]/img', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/golem.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/golem.de.php new file mode 100644 index 0000000..8731285 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/golem.de.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.golem.de/news/breko-telekom-verzoegert-gezielt-den-vectoring-ausbau-1311-102974.html', + 'body' => array( + '//header[@class="cluster-header"]', + '//header[@class="paged-cluster-header"]', + '//div[@class="formatted"]', + ), + 'next_page' => array( + '//a[@id="atoc_next"]' + ), + 'strip' => array( + '//header[@class="cluster-header"]/a', + '//header[@class="cluster-header"]/h1', + '//div[@id="iqadtile4"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gondola.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gondola.hu.php new file mode 100644 index 0000000..62b1e3c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gondola.hu.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'https://gondola.hu/hirek/213754-A_budapesti_fejlesztesek_kerdese_mar_nem_zsakbamacska.html', + 'body' => array( + '//div[@id="cikk"]/div[@class="cim"]', + '//br[1]', + '//div[@class="alcim"]', + '//div[@class="lead"]', + '//div[@class="szoveg"]' + ), + 'strip' => array( + '//div[@class="ikonok"]', + '//div[@class="linkekblokk"]', + '//div[@id="billboardbanner"]', + '//div[@class="szerzo"]', + '//div[@class="kulcsszavak"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gorabbit.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gorabbit.ru.php new file mode 100644 index 0000000..4e43248 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/gorabbit.ru.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://gorabbit.ru/article/10-oshchushcheniy-za-rulem-kogda-tolko-poluchil-voditelskie-prava', + 'body' => array( + '//div[@class="detail_text"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//div[@class="socials"]', + '//div[@id="cr_1"]', + '//div[@class="related_items"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/habrahabr.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/habrahabr.ru.php new file mode 100644 index 0000000..3f1ec16 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/habrahabr.ru.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'https://habrahabr.ru/company/pentestit/blog/328606/', + 'body' => array( + "//div[contains(concat(' ',normalize-space(@class),' '),' content ')]" + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/happletea.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/happletea.com.php new file mode 100644 index 0000000..75b0b83 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/happletea.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'body' => array( + '//div[@id="comic"]', + '//div[@class="entry"]', + ), + 'strip' => array('//div[@class="ssba"]'), + 'test_url' => 'http://www.happletea.com/comic/mans-best-friend/', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hardware.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hardware.fr.php new file mode 100644 index 0000000..56aec4f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hardware.fr.php @@ -0,0 +1,11 @@ + array( + '%^/news.*%' => array( + 'test_url' => 'http://www.hardware.fr/news/14760/intel-lance-nouveaux-ssd-nand-3d.html', + 'body' => array( + '//div[@class="content_actualite"]/div[@class="md"]', + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/heise.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/heise.de.php new file mode 100644 index 0000000..0ee6915 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/heise.de.php @@ -0,0 +1,79 @@ + array( + '%^/tp.*%' => array( + 'test_url' => 'https://www.heise.de/tp/features/Macrons-Vermoegenssteuer-Der-Staat-verzichtet-auf-3-2-Milliarden-3863931.html', + 'body' => array( + '//main/article' + ), + 'strip' => array( + '//header', + '//aside', + '//nav[@class="pre-akwa-toc"]', + '//*[@class="seite_zurueck"]', + '//*[@class="pagination"]', + '//a[@class="kommentare_lesen_link"]', + '//div[contains(@class, "shariff")]', + '//a[@class="beitragsfooter_permalink"]', + '//a[@class="beitragsfooter_fehlermelden"]', + '//a[@class="beitragsfooter_printversion"]' + ), + 'next_page' => array( + '//a[@class="seite_weiter"]' + ), + ), + '%^/newsticker/meldung.*%' => array( + 'test_url' => 'https://www.heise.de/newsticker/meldung/DragonFly-BSD-5-0-mit-experimentellem-HAMMER2-veroeffentlicht-3864731.html', + 'body' => array( + '//div[@class="article-content"]', + ), + 'strip' => array( + '//*[contains(@class, "gallery")]', + '//*[contains(@class, "video")]', + ), + ), + '%^/autos/artikel.*%' => array( + 'test_url' => 'https://www.heise.de/autos/artikel/Bericht-Mazda-baut-Range-Extender-mit-Wankelmotor-3864760.html', + 'body' => array( + '//section[@id="artikel_text"]' + ), + 'strip' => array( + '//p[@id="content_foren"]', + '//div[contains(@class, "shariff")]', + '//p[@class="permalink"]', + '//p[@class="printversion"]' + ), + ), + '%^/foto/meldung.*%' => array( + 'test_url' => 'https://www.heise.de/foto/meldung/Wildlife-Fotograf-des-Jahres-Gewinnerbild-zeigt-getoetetes-Nashorn-3864311.html', + 'body' => array( + '//div[@class="article-content"]' + ), + ), + '%^/ct.*%' => array( + 'test_url' => 'https://www.heise.de/ct/artikel/Google-Pixel-2-und-Pixel-2-XL-im-Test-3863842.html', + 'body' => array( + '//main/div[1]/div[1]/section' + ), + 'strip' => array( + '//header' + ) + ), + '%^/developer.*%' => array( + 'test_url' => 'https://www.heise.de/developer/meldung/Container-Docker-unterstuetzt-Kubernetes-3863625.html', + 'body' => array( + '//div[@class="article-content"]' + ) + ), + '%.*%' => array( + 'test_url' => 'https://www.heise.de/mac-and-i/meldung/iOS-App-Nude-findet-mittels-ML-Nacktbilder-und-versteckt-sie-3864217.html', + 'body' => array( + '//article/div[@class="meldung_wrapper"]', + ), + 'strip' => array( + '//*[contains(@class, "gallery")]', + '//*[contains(@class, "video")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hirek.prim.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hirek.prim.hu.php new file mode 100644 index 0000000..1a38809 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hirek.prim.hu.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://hirek.prim.hu/cikk/2017/10/02/atadtak_a_6_fenntarthatosagi_sajtodijat', + 'body' => array( + '//div[@class="boxbody article_box"]/h2', + '//div[@class="text_body"]' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hotshowlife.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hotshowlife.com.php new file mode 100644 index 0000000..faf01f3 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hotshowlife.com.php @@ -0,0 +1,23 @@ + array( + '%.*%' => array( + 'test_url' => 'https://hotshowlife.com/top-10-chempionov-produktov-po-szhiganiyu-kalorij/', + 'body' => array( + '//div[@class="entry-content"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//div[@class="ads2"]', + '//div[@class="mistape_caption"]', + '//div[contains(@class, "et_social_media_hidden")]', + '//div[contains(@class, "et_social_inline_bottom")]', + '//div[contains(@class, "avatar")]', + '//ul[contains(@class, "entry-tags")]', + '//div[contains(@class, "entry-meta")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/huffingtonpost.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/huffingtonpost.com.php new file mode 100644 index 0000000..b52b07b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/huffingtonpost.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.huffingtonpost.com/2014/02/20/centscere-social-media-syracuse_n_4823848.html', + 'body' => array( + '//article[@class="content")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hvg.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hvg.hu.php new file mode 100644 index 0000000..efc9371 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/hvg.hu.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://hvg.hu/brandchannel/mastercardbch_20171006_Egyetlen_mobillal_erintettuk_Budapest_legjobb_gasztrohelyeit', + 'body' => array( + '//div[@class="article-title article-title"]', + '//div[@class="article-cover-img"]', + '//div[@class="article-main"]' + ), + 'strip' => array( + '//figcaption', + '//div[@class="article-info byline"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/idokep.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/idokep.hu.php new file mode 100644 index 0000000..06cae09 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/idokep.hu.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.idokep.hu/hirek/4-es-erossegu-tajfun-tart-japan-fele', + 'body' => array( + '//div[@class="cikk-title"]/h3', + '//div[@class="lead"]', + '//div[@class="atvett_tartalom"]', + '//div[@class="cikk-tartalom"]' + ), + 'strip' => array( + '//div[@class="cimkes-doboz"]', + '//div[@class="komment-wrap"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/imogenquest.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/imogenquest.net.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/imogenquest.net.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/index.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/index.hu.php new file mode 100644 index 0000000..727d7b7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/index.hu.php @@ -0,0 +1,29 @@ + array( + '%.*%' => array( + 'test_url' => 'http://index.hu/mindekozben/poszt/2017/10/20/art_deco_budapest_varosnezo_zsebkonyv_bolla_zoltan/', + 'body' => array( + '//div[@class="mindenkozben_post_content content"]', + '//div[@id="content"]' + ), + 'strip' => array( + '//div[@class="topszponzor_wrapper"]', + '//ul[@class="cikk-cimkek"]', + '//div[@class="author-share-date-container"]', + '//div[@class="pp-list"]', + '//div[@class="social-stripe cikk-bottom-box"]', + '//div[@class="cikk-bottom-text-ad"]', + '//a[@name="hozzaszolasok"]', + '//div[@class="cikk-vegi-ajanlo-reklamok-container"]', + '//div[@id="comments"]', + '//div[@class="comments"]', + '//div[@class="linkpreview-box bekezdes_utan"]', + '//div[@class="lapozo"]', + '//div[@class="szelso-jobb"]', + '//div[@class="social cikk-bottom-box"]', + '//input' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/indiehaven.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/indiehaven.com.php new file mode 100644 index 0000000..a40ce69 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/indiehaven.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://indiehaven.com/no-mans-sky-is-a-solo-space-adventure-and-im-ok-with-that/', + 'body' => array( + '//section[contains(@class, "entry-content")]', + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/inforadio.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/inforadio.hu.php new file mode 100644 index 0000000..569013d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/inforadio.hu.php @@ -0,0 +1,25 @@ + array( + '%.*%' => array( + 'test_url' => 'http://inforadio.hu/belfold/2017/10/20/fontos_valtozas_vegleg_lezarnak_tobb_villamosatjarot_budapesten/', + 'body' => array( + '//div[@class="content-title"]', + '//div[@class="szelso-jobb-lead_container"]', + '//div[@class="cikk-torzs"]' + ), + 'strip' => array( + '//div[@id="microsite_microsite"]', + '//div[@class="cikk-bottom-text-ad"]', + '//div[@class="social-stripe_container"]', + '//div[@class="facebook-like-box"]', + '//div[@class="rovat sargabg rovatdobozcim"]', + '//div[@class="m-okosradio_magazin arenaMagazineItem"]', + '//header[@class="m-okosradio_header"]', + '//div[@class="m-okosradio_elo"]', + '//div[@class="m-okosradio_container"]', + '//form' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ing.dk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ing.dk.php new file mode 100644 index 0000000..5a021a0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ing.dk.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://ing.dk/artikel/smart-husisolering-og-styring-skal-mindske-japans-energikrise-164517', + 'body' => array( + '//section[contains(@class, "teaser")]', + '//section[contains(@class, "body")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/invisiblebread.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/invisiblebread.com.php new file mode 100644 index 0000000..90f8759 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/invisiblebread.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%()%' => '$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ir.amd.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ir.amd.com.php new file mode 100644 index 0000000..af99fe9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ir.amd.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'body' => array('//span[@class="ccbnTxt"]'), + 'strip' => array(), + 'test_url' => 'http://ir.amd.com/phoenix.zhtml?c=74093&p=RssLanding&cat=news&id=2055819', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantimes.co.jp.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantimes.co.jp.php new file mode 100644 index 0000000..9959441 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantimes.co.jp.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.japantimes.co.jp/news/2015/09/27/world/social-issues-world/pope-meets-sex-abuse-victims-philadelphia-promises-accountability/', + 'body' => array( + '//article[@role="main"]', + ), + 'strip' => array( + '//script', + '//header', + '//div[contains(@class, "meta")]', + '//div[@class="clearfix"]', + '//div[@class="OUTBRAIN"]', + '//ul[@id="content_footer_menu"]', + '//div[@class="article_footer_ad"]', + '//div[@id="disqus_thread"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantoday.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantoday.com.php new file mode 100644 index 0000000..22485d6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/japantoday.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.japantoday.com/category/politics/view/japan-u-s-to-sign-new-base-environment-pact', + 'body' => array( + '//div[@id="article_container"]', + ), + 'strip' => array( + '//h2', + '//div[@id="article_info"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/journaldugeek.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/journaldugeek.com.php new file mode 100644 index 0000000..876b269 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/journaldugeek.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www./2014/05/20/le-playstation-now-arrive-en-beta-fermee-aux-etats-unis/', + 'body' => array( + '//div[@class="post-content"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/jsonline.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/jsonline.com.php new file mode 100644 index 0000000..5895256 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/jsonline.com.php @@ -0,0 +1,37 @@ + array( + '%.%/picture-gallery/%' => array( + 'test_url' => 'http://www.jsonline.com/picture-gallery/news/local/milwaukee/2017/02/22/photos-aclu-sues-milwaukee-police-over-profiling-stop-and-frisk/98250836/', + 'body' => array( + '//div[@class="priority-asset-gallery galleries standalone hasendslate"]', + ), + 'strip' => array( + '//div[@class="buy-photo-btn"]', + '//div[@class="gallery-thumbs thumbs pag-thumbs")]', + ), + ), + '%.*%' => array( + 'test_url' => 'http://www.jsonline.com/news/usandworld/as-many-as-a-million-expected-for-popes-last-mass-in-us-b99585180z1-329688131.html', + 'body' => array( + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//h1', + '//iframe', + '//span[@class="mycapture-small-btn mycapture-btn-with-text mycapture-expandable-photo-btn-small js-mycapture-btn-small"]', + '//div[@class="close-wrap"]', + '//div[contains(@class,"ui-video-wrapper")]', + '//div[contains(@class,"media-mob")]', + '//div[contains(@class,"left-mob")]', + '//div[contains(@class,"nerdbox")]', + '//p/span', + '//div[contains(@class,"oembed-asset")]', + '//*[contains(@class,"share")]', + '//div[contains(@class,"gallery-asset")]', + '//div[contains(@class,"oembed-asset")]', + '//div[@class="article-print-url"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/justcoolidea.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/justcoolidea.ru.php new file mode 100644 index 0000000..089ff29 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/justcoolidea.ru.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://justcoolidea.ru/idealnyj-sad-samodelnye-proekty-dlya-berezhlivogo-domovladeltsa/', + 'body' => array( + '//section[@class="entry-content"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[contains(@class, "essb_links")]', + '//*[contains(@rel, "nofollow")]', + '//*[contains(@class, "ads")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kanpai.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kanpai.fr.php new file mode 100644 index 0000000..c3a1abc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kanpai.fr.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.kanpai.fr/japon/comment-donner-lheure-en-japonais.html', + 'body' => array( + '//div[@class="single-left"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/karriere.jobfinder.dk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/karriere.jobfinder.dk.php new file mode 100644 index 0000000..25d6dfa --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/karriere.jobfinder.dk.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://karriere.jobfinder.dk/artikel/dansk-professor-skal-lede-smart-grid-forskning-20-millioner-dollars-763', + 'body' => array( + '//section[contains(@class, "teaser")]', + '//section[contains(@class, "body")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kisalfold.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kisalfold.hu.php new file mode 100644 index 0000000..7568901 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kisalfold.hu.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.kisalfold.hu/szorakozas/egy_15_eves_srac_szuntetheti_meg_a_wc-parat_budapesten/2536699/', + 'body' => array( + '//header[@class="single-article__header"]/h1', + '//header[@class="single-article__header"]/h2', + '//figure[@class="single-article__image"]/img', + '//div[@class="single-article__content"]/div[@id="single-article__lead"]', + '//div[@id="article_text"]' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kiszamolo.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kiszamolo.hu.php new file mode 100644 index 0000000..c44a08f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kiszamolo.hu.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'https://kiszamolo.hu/30-eve-volt-a-fekete-hetfo/', + 'body' => array( + '//article/h2', + '//article/div[@class="entry clearfix"]/p' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kodi.tv.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kodi.tv.php new file mode 100644 index 0000000..439fc90 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/kodi.tv.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'https://kodi.tv/article/andwere-baaaaack', + 'body' => array( + '//div[@class="l-region--content"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreaherald.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreaherald.com.php new file mode 100644 index 0000000..9651056 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreaherald.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.koreaherald.com/view.php?ud=20150926000018', + 'body' => array( + '//div[@id="articleText"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreatimes.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreatimes.php new file mode 100644 index 0000000..f274b4a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/koreatimes.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.koreatimes.co.kr/www/news/nation/2015/12/116_192409.html', + 'body' => array( + '//div[@id="p"]', + ), + 'strip' => array( + '//div[@id="webtalks_btn_listenDiv"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lastplacecomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lastplacecomics.com.php new file mode 100644 index 0000000..12697cc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lastplacecomics.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/legorafi.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/legorafi.fr.php new file mode 100644 index 0000000..e6aae46 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/legorafi.fr.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => array( + 'http://www.legorafi.fr/2016/12/16/gorafi-magazine-bravo-vous-avez-bientot-presque-survecu-a-2016/', + 'http://www.legorafi.fr/2016/12/15/manuel-valls-promet-quune-fois-elu-il-debarrassera-la-france-de-manuel-valls/', + ), + 'body' => array( + '//section[@id="banner_magazine"]', + '//figure[@class="main_picture"]', + '//div[@class="content"]', + ), + 'strip' => array( + '//figcaption', + '//div[@class="sharebox"]', + '//div[@class="tags"]', + '//section[@class="taboola_article"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lejapon.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lejapon.fr.php new file mode 100644 index 0000000..8f2b293 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lejapon.fr.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://lejapon.fr/guide-voyage-japon/5223/tokyo-sous-la-neige.htm', + 'body' => array( + '//div[@class="entry"]', + ), + 'strip' => array( + '//*[contains(@class, "addthis_toolbox")]', + '//*[contains(@class, "addthis_default_style")]', + '//*[@class="navigation small"]', + '//*[@id="related"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lesjoiesducode.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lesjoiesducode.fr.php new file mode 100644 index 0000000..369206a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lesjoiesducode.fr.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://lesjoiesducode.fr/post/75576211207/quand-lappli-ne-fonctionne-plus-sans-aucune-raison', + 'body' => array( + '//div[@class="blog-post-content"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lfg.co.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lfg.co.php new file mode 100644 index 0000000..d978a5f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lfg.co.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.lfg.co/page/871/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+LookingForGroup+%28Looking+For+Group%29&utm_content=FeedBurner', + 'body' => array( + '//*[@id="comic"]/img | //*[@class="content"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.com.php new file mode 100644 index 0000000..b9a6933 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://lifehacker.com/bring-water-bottle-caps-into-concerts-to-protect-your-d-1269334973', + 'body' => array( + '//div[contains(@class, "row")/img', + '//div[contains(@class, "content-column")]', + ), + 'strip' => array( + '//*[contains(@class, "meta")]', + '//span[contains(@class, "icon")]', + '//h1', + '//aside', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.ru.php new file mode 100644 index 0000000..bc140f6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lifehacker.ru.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://lifehacker.ru/2016/03/03/polymail/', + 'body' => array( + '//div[@class="post-content"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[@class="wp-thumbnail-caption"]', + '//*[contains(@class, "social-likes")]', + '//*[@class="jp-relatedposts"]', + '//*[contains(@class, "wpappbox")]', + '//*[contains(@class, "icon__image")]', + '//div[@id="hypercomments_widget"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux-magazin.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux-magazin.de.php new file mode 100644 index 0000000..f4bc07d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux-magazin.de.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.linux-magazin.de/Ausgaben/2017/09/AWS-Alternativen', + 'body' => array( + '//div[@class="attribute-content"]/div[@class="attribute-intro"]', + '(//div[@class="attribute-image"])[1]', + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//p[@class="attribute-advice"]', + ) + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.php new file mode 100644 index 0000000..2520d0d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.linux.org/threads/lua-the-scripting-interpreter.8352/', + 'body' => array( + '//div[@class="messageContent"]', + ), + 'strip' => array( + '//aside', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.ru.php new file mode 100644 index 0000000..7fa0249 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linux.org.ru.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.linux.org/threads/lua-the-scripting-interpreter.8352/', + 'body' => array( + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linuxinsider.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linuxinsider.com.php new file mode 100644 index 0000000..4e0a4cc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/linuxinsider.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.linuxinsider.com/story/82526.html?rss=1', + 'body' => array( + '//div[@id="story"]', + ), + 'strip' => array( + '//script', + '//h1', + '//div[@id="story-toolbox1"]', + '//div[@id="story-byline"]', + '//div[@id="story"]/p', + '//div[@class="story-advertisement"]', + '//iframe', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lists.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lists.php new file mode 100644 index 0000000..c7051a2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lists.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://lists.freebsd.org/pipermail/freebsd-announce/2013-September/001504.html', + 'body' => array( + '//pre', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loadingartist.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loadingartist.com.php new file mode 100644 index 0000000..d06ed12 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loadingartist.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loldwell.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loldwell.com.php new file mode 100644 index 0000000..d358e15 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/loldwell.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://loldwell.com/?comic=food-math-101', + 'body' => array('//*[@id="comic"]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lukesurl.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lukesurl.com.php new file mode 100644 index 0000000..816233d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/lukesurl.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'body' => array('//div[@id="comic"]//img'), + 'strip' => array(), + 'test_url' => 'http://www.lukesurl.com/archives/comic/665-3-of-clubs', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/macg.co.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/macg.co.php new file mode 100644 index 0000000..bbe6dbc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/macg.co.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.macg.co//logiciels/2014/05/feedly-sameliore-un-petit-peu-sur-mac-82205', + 'body' => array( + '//div[contains(@class, "field-name-body")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maclife.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maclife.de.php new file mode 100644 index 0000000..bca347e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maclife.de.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.maclife.de/news/neue-farbe-iphone-8-kommt-blush-gold-10094817.html', + 'body' => array( + '//div[contains(@class, "article_wrapper")]/p | //div[contains(@class, "article_wrapper")]/h2 | //div[@class="gallery"]//figure | //div[contains(@class, "gallery_single")]//figure', + ) + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/magyarkurir.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/magyarkurir.hu.php new file mode 100644 index 0000000..32d78bc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/magyarkurir.hu.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.magyarkurir.hu/hirek/a-vilagszerte-ismert-dicsoito-csapat-hillsong-young-free-lep-fel-budapesten', + 'body' => array( + '//div[@class="behuzas"]' + ), + 'strip' => array( + '//div[@class="ikonsav"]', + '//p[@class="copyright"]', + '//div[@class="cimkek"]', + '//div[@id="footerbanner"]', + '//div[@class="rovat sargabg rovatdobozcim"]', + '//div[@class="rovatdoboz"]', + '//a[contains(., "Own")]', + '//a[@class="fblink"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marc.info.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marc.info.php new file mode 100644 index 0000000..5f582a6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marc.info.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://marc.info/?l=openbsd-misc&m=141987113202061&w=2', + 'body' => array( + '//pre', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marriedtothesea.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marriedtothesea.com.php new file mode 100644 index 0000000..469640d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marriedtothesea.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.marriedtothesea.com/index.php?date=052915', + 'body' => array( + '//div[@align]/a/img', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marycagle.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marycagle.com.php new file mode 100644 index 0000000..b8665e3 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/marycagle.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'body' => array( + '//img[@id="cc-comic"]', + '//div[@class="cc-newsbody"]', + ), + 'strip' => array(), + 'test_url' => 'http://www.marycagle.com/letsspeakenglish/74-grim-reality/', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maximumble.thebookofbiff.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maximumble.thebookofbiff.com.php new file mode 100644 index 0000000..8880054 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/maximumble.thebookofbiff.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://maximumble.thebookofbiff.com/2015/04/20/1084-change/', + 'body' => array('//div[@id="comic"]/div/a/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/medium.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/medium.com.php new file mode 100644 index 0000000..e20860e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/medium.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'https://medium.com/lessons-learned/917b8b63ae3e', + 'body' => array( + '//div[@class="section-content"]', + ), + 'strip' => array( + '//div[contains(@class,"metabar")]', + '//img[contains(@class,"thumbnail")]', + '//h1', + '//blockquote', + '//div[@class="aspectRatioPlaceholder-fill"]', + '//footer' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mercworks.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mercworks.net.php new file mode 100644 index 0000000..c7a27de --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mercworks.net.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'body' => array('//div[@id="comic"]', + '//div[contains(@class,"entry-content")]', + ), + 'strip' => array(), + 'test_url' => 'http://mercworks.net/comicland/healthy-choice/', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/metronieuws.nl.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/metronieuws.nl.php new file mode 100644 index 0000000..5011169 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/metronieuws.nl.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.metronieuws.nl/sport/2015/04/broer-fellaini-zorgde-bijna-voor-paniek-bij-mourinho', + 'body' => array('//div[contains(@class,"article-top")]/div[contains(@class,"image-component")] | //div[@class="article-full-width"]/div[1]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/milwaukeenns.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/milwaukeenns.php new file mode 100644 index 0000000..ddb29a5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/milwaukeenns.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://milwaukeenns.org/2016/01/08/united-way-grant-enables-sdc-to-restore-free-tax-assistance-program/', + 'body' => array( + '//div[@class="pf-content"]', + ), + 'strip' => array( + '//div[@class="printfriendly"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mno.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mno.hu.php new file mode 100644 index 0000000..a2799b8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mno.hu.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'https://mno.hu/kulfold/elnokot-valasztanak-szloveniaban-2422840', + 'body' => array( + '//div[@class="header"]/h1', + '//div[@class="content hircikk clearfix"]/p' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mokepon.smackjeeves.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mokepon.smackjeeves.com.php new file mode 100644 index 0000000..1ddcd40 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mokepon.smackjeeves.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://mokepon.smackjeeves.com/comics/2120096/chapter-9-page-68/', + 'body' => array('//*[@id="comic_area_inner"]/img | //*[@id="comic_area_inner"]/a/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monandroid.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monandroid.com.php new file mode 100644 index 0000000..f87560e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monandroid.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.monandroid.com/blog/tutoriel-avance-activer-le-stockage-fusionne-sur-android-6-marshamallow-t12.html', + 'body' => array( + '//div[@class="blog-post-body"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monwindows.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monwindows.com.php new file mode 100644 index 0000000..b2b24d7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/monwindows.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.monwindows.com/tout-savoir-sur-le-centre-d-action-de-windows-phone-8-1-t40574.html', + 'body' => array( + '//div[@class="blog-post-body"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/moya-planeta.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/moya-planeta.ru.php new file mode 100644 index 0000000..dd84284 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/moya-planeta.ru.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.moya-planeta.ru/travel/view/chto_yaponcu_horosho_russkomu_ne_ponyat_20432/', + 'body' => array( + '//div[@class="full_object"]', + ), + 'strip' => array( + '//div[@class="full_object_panel object_panel"]', + '//div[@class="full_object_panel_geo object_panel"]', + '//div[@class="full_object_title"]', + '//div[@class="full_object_social_likes"]', + '//div[@class="full_object_planeta_likes"]', + '//div[@class="full_object_go2comments"]', + '//div[@id="yandex_ad_R-163191-3"]', + '//div[@class="full_object_shop_article_recommend"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mrlovenstein.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mrlovenstein.com.php new file mode 100644 index 0000000..b971091 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mrlovenstein.com.php @@ -0,0 +1,9 @@ + array( + '%.*%' => array( + '%alt="(.+)" */>%' => '/>
$1', + '%\.png%' => '_rollover.png', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/muckrock.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/muckrock.com.php new file mode 100644 index 0000000..9e354a3 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/muckrock.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.muckrock.com/news/archives/2016/jan/13/5-concerns-private-prisons/', + 'body' => array( + '//div[@class="content"]', + ), + 'strip' => array( + '//div[@class="newsletter-widget"]', + '//div[@class="contributors"]', + '//time', + '//h1', + '//div[@class="secondary"]', + '//aside', + '//div[@class="articles__related"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mynorthshorenow.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mynorthshorenow.com.php new file mode 100644 index 0000000..b630915 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/mynorthshorenow.com.php @@ -0,0 +1,27 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.mynorthshorenow.com/story/news/local/fox-point/2017/04/04/fox-point-building-board-approves-dunwood-commons-project/99875570/', + 'body' => array( + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//h1', + '//iframe', + '//span[@class="mycapture-small-btn mycapture-btn-with-text mycapture-expandable-photo-btn-small js-mycapture-btn-small"]', + '//div[@class="close-wrap"]', + '//div[contains(@class,"ui-video-wrapper")]', + '//div[contains(@class,"media-mob")]', + '//div[contains(@class,"left-mob")]', + '//div[contains(@class,"nerdbox")]', + '//p/span', + '//div[contains(@class,"oembed-asset")]', + '//*[contains(@class,"share")]', + '//div[contains(@class,"gallery-asset")]', + '//div[contains(@class,"oembed-asset")]', + '//div[@class="article-print-url"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nakedCapitalism.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nakedCapitalism.php new file mode 100644 index 0000000..ec2d5fd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nakedCapitalism.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://feedproxy.google.com/~r/NakedCapitalism/~3/JOBxEHxN8ZI/mark-blyth-liberalism-undermined-democracy-failure-democratic-party.html', + 'body' => array( + '//div[@class="pf-content"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nasa.gov.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nasa.gov.php new file mode 100644 index 0000000..c6692d0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nasa.gov.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.nasa.gov/image-feature/jpl/pia20514/coy-dione', + 'body' => array( + '//div[@class="article-body"]', + ), + 'strip' => array( + '//div[@class="title-bar"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nat-geo.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nat-geo.ru.php new file mode 100644 index 0000000..1a42d99 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nat-geo.ru.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nat-geo.ru/fact/868093-knidos-antichnyy-naukograd/', + 'body' => array( + '//div[@class="article-inner-text"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nationaljournal.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nationaljournal.com.php new file mode 100644 index 0000000..5e612be --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nationaljournal.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nationaljournal.com/s/354962/south-carolina-evangelicals-outstrip-establishment?mref=home_top_main', + 'body' => array( + '//div[@class="section-body"]', + ), + 'strip' => array( + '//*[contains(@class, "-related")]', + '//*[contains(@class, "social")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nature.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nature.com.php new file mode 100644 index 0000000..6b9e87f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nature.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nature.com/doifinder/10.1038/nature.2015.18340', + 'body' => array( + '//div[contains(@class,"main-content")]', + ), + 'strip' => array(), + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nba.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nba.com.php new file mode 100644 index 0000000..c8ea926 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nba.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nba.com/2015/news/09/25/knicks-jackson-to-spend-more-time-around-coaching-staff.ap/index.html?rss=true', + 'body' => array( + '//div[@class="paragraphs"]', + ), + 'strip' => array( + '//div[@id="nbaArticleSocialWrapper_bot"]', + '//h5', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nedroid.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nedroid.com.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nedroid.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/networkworld.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/networkworld.com.php new file mode 100644 index 0000000..1852435 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/networkworld.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.networkworld.com/article/3020585/security/the-incident-response-fab-five.html', + 'body' => array( + '//figure/img[@class="hero-img"]', + '//section[@class="deck"]', + '//div[@itemprop="articleBody"] | //div[@itemprop="reviewBody"]', + '//div[@class="carousel-inside-crop"]', + ), + 'strip' => array( + '//script', + '//aside', + '//div[@class="credit"]', + '//div[@class="view-large"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/neustadt-ticker.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/neustadt-ticker.de.php new file mode 100644 index 0000000..e0c0d19 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/neustadt-ticker.de.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.neustadt-ticker.de/41302/alltag/kultur/demo-auf-der-boehmischen', + 'body' => array( + '//div[@class="entry-content"]', + ), + 'strip' => array( + '//*[contains(@class, "sharedaddy")]', + '//*[contains(@class, "yarpp-related")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nextinpact.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nextinpact.com.php new file mode 100644 index 0000000..29dd9d6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nextinpact.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nextinpact.com/news/101122-3d-nand-intel-lance-six-nouvelles-gammes-ssd-pour-tous-usages.htm', + 'body' => array( + '//div[@class="container_article"]', + ), + 'strip' => array( + '//div[@class="infos_article"]', + '//div[@id="actu_auteur"]', + '//div[@id="soutenir_journaliste"]', + '//section[@id="bandeau_abonnez_vous"]', + '//br' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/niceteethcomic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/niceteethcomic.com.php new file mode 100644 index 0000000..f41e443 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/niceteethcomic.com.php @@ -0,0 +1,10 @@ + array( + '%/archives.*%' => array( + 'test_url' => 'http://niceteethcomic.com/archives/page119/', + 'body' => array('//*[@class="comicpane"]/a/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nichtlustig.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nichtlustig.de.php new file mode 100644 index 0000000..4d083f9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nichtlustig.de.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%.*static.nichtlustig.de/comics/full/(\\d+).*%s' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nlcafe.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nlcafe.hu.php new file mode 100644 index 0000000..b85e6b2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/nlcafe.hu.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.nlcafe.hu/ezvan/20171021/nyugdijas-drogdilert-fogtak-a-ferencvarosi-rendorok/', + 'body' => array( + '//div[@class="single-title"]', + '//div[@class="single-excerpt"]', + '//div[@class="single-post-container-content"]/p', + '//div[@class="single-post-container-content"]/div' + ), + 'strip' => array( + '//div[@class="widget-container related-articles bigdata-widget related-full"]', + '//div[@class="banner-container clear-banner-row clearfix"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/novo-argumente.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/novo-argumente.com.php new file mode 100644 index 0000000..cef3595 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/novo-argumente.com.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.novo-argumente.com/artikel/der_kampf_gegen_die_schlafkrankheit', + 'body' => array( + '//main/div/article', + ), + 'strip' => array( + '//*[@class="artikel-datum"]', + '//*[@class="artikel-titel"]', + '//*[@class="artikel-autor"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/oglaf.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/oglaf.com.php new file mode 100644 index 0000000..8b2b5b6 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/oglaf.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'body' => array( + '//img[@id="strip"]', + '//a/div[@id="nx"]/..', + ), + 'strip' => array(), + 'test_url' => 'http://oglaf.com/slodging/', + ), + ), + 'filter' => array( + '%.*%' => array( + '%alt="(.+)" title="(.+)" */>%' => '/>
$1
$2
', + '%%' => 'Next page', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onhax.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onhax.net.php new file mode 100644 index 0000000..213849d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onhax.net.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://onhax.net/process-lasso-8-9-1-4-pro-key-portable-is-here-latest', + 'body' => array( + '//div[@class="postcontent"]', + ), + 'strip' => array( + '//*[@class="sharedaddy sd-sharing-enabled"]', + '//*[@class="yarpp-related"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onlinekosten.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onlinekosten.de.php new file mode 100644 index 0000000..7382612 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onlinekosten.de.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.onlinekosten.de/news/android-8-0-die-neuen-features-im-ueberblick_209619.html?utm_source=rss&utm_medium=feed&utm_campaign=android-8-0-die-neuen-features-im-ueberblick', + 'body' => array( + '//p[@class="cms-widget_article_lead"]', + '//img[@class="bec_img"]', + '//div[@class="cms-widget_article_body"]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onmilwaukee.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onmilwaukee.php new file mode 100644 index 0000000..f66ac4b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/onmilwaukee.php @@ -0,0 +1,24 @@ + array( + '%.*%' => array( + 'test_url' => 'http://onmilwaukee.com/movies/articles/downerspelunking.html', + 'body' => array( + '//article[contains(@class, "show")]', + ), + 'strip' => array( + '//h1', + '//div[contains(@class,"-ad")]', + '//div[contains(@class,"_ad")]', + '//div[@id="pub_wrapper"]', + '//div[contains(@class,"share_tools")]', + '//div[@class="clearfix"]', + '//div[contains(@class,"image_control")]', + '//section[@class="ribboned"]', + '//div[contains(@class,"sidebar")]', + '//aside[@class="article_tag_list"]', + '//section[contains(@id,"more_posts")]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openculture.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openculture.com.php new file mode 100644 index 0000000..84f2bee --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openculture.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.openculture.com/2017/03/are-we-living-inside-a-computer-simulation-watch-the-simulation-argument.html', + 'body' => array( + '//div[@class="entry"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opennet.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opennet.ru.php new file mode 100644 index 0000000..1fb7722 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opennet.ru.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.opennet.ru/opennews/art.shtml?num=46549', + 'body' => array( + '//*[@id="r_memo"]', + ), + 'strip' => array( + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openrightsgroup.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openrightsgroup.org.php new file mode 100644 index 0000000..94139a7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/openrightsgroup.org.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.openrightsgroup.org/blog/2014/3-days-to-go-till-orgcon2014', + 'body' => array( + '//div[contains(@class, "content")]/div', + ), + 'strip' => array( + '//h2[1]', + '//div[@class="info"]', + '//div[@class="tags"]', + '//div[@class="comments"]', + '//div[@class="breadcrumbs"]', + '//h1[@class="pageTitle"]', + '//p[@class="bookmarkThis"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opensource.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opensource.com.php new file mode 100644 index 0000000..60f3577 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/opensource.com.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://opensource.com/life/15/10/how-internet-things-will-change-way-we-think', + 'body' => array( + '//div[@id="article-template"]', + ), + 'strip' => array( + '//div[contains(@class,"os-article__sidebar")]', + '//div[@class="panel-pane pane-node-title"]', + '//div[@class="panel-pane pane-os-article-byline"]', + '//ul', + '//div[contains(@class,"-license")]', + '//div[contains(@class,"-tags")]', + '//div[@class="panel-pane pane-os-article-byline"]', + '//div[@class="os-article__content-below"]', + '//div[@id="comments"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/optipess.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/optipess.com.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/optipess.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/origo.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/origo.hu.php new file mode 100644 index 0000000..7bd9496 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/origo.hu.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.origo.hu/itthon/20171019-hamisan-tanuskodott-az-ugyved-ezert-nem-praktizalhat.html', + 'body' => array( + '//header[@id="article-head"]/h1', + '//article[@id="article-center"]' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/osnews.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/osnews.com.php new file mode 100644 index 0000000..1d1396c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/osnews.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://osnews.com/story/28863/Google_said_to_be_under_US_antitrust_scrutiny_over_Android', + 'body' => array( + '//div[@class="newscontent1"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pastebin.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pastebin.com.php new file mode 100644 index 0000000..b20bf41 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pastebin.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://pastebin.com/ed1pP9Ak', + 'body' => array( + '//div[@class="text"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pcgameshardware.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pcgameshardware.de.php new file mode 100644 index 0000000..f180aee --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pcgameshardware.de.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.pcgameshardware.de/Dragon-Age-Thema-259929/News/Plaene-fuer-Teil-4-und-5-der-Serie-1235682/', + 'body' => array( + '//p[@class="introText"]', + '//figure[contains(@class, "articleBigTeaser")]', + '//div[@id="articleTextBody"]//p | //div[@id="articleTextBody"]//h2[@class="anchorHeadline"]', + ), + 'strip' => array( + '//p[@class="introText"]//time', + ) + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/peebleslab.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/peebleslab.com.php new file mode 100644 index 0000000..ce4891d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/peebleslab.com.php @@ -0,0 +1,9 @@ + array( + '%.*%' => array( + // the extra space is required to strip the title cleanly + '%title="(.+) " */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/penny-arcade.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/penny-arcade.com.php new file mode 100644 index 0000000..dd39983 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/penny-arcade.com.php @@ -0,0 +1,21 @@ + array( + '%/news/.*%' => array( + 'test_url' => 'http://penny-arcade.com/news/post/2015/04/15/101-part-two', + 'body' => array( + '//*[@class="postBody"]/*', + ), + 'strip' => array( + ), + ), + '%/comic/.*%' => array( + 'test_url' => 'http://penny-arcade.com/comic/2015/04/15', + 'body' => array( + '//*[@id="comicFrame"]/a/img', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pixelbeat.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pixelbeat.org.php new file mode 100644 index 0000000..fa9052e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pixelbeat.org.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.pixelbeat.org/programming/sigpipe_handling.html#1425573246', + 'body' => array( + '//div[@class="contentText"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/plus.google.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/plus.google.com.php new file mode 100644 index 0000000..5e48a6c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/plus.google.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'https://plus.google.com/+LarryPage/posts/Lh8SKC6sED1', + 'body' => array( + '//div[@role="article"]/div[contains(@class, "eE")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/popstrip.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/popstrip.com.php new file mode 100644 index 0000000..801a281 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/popstrip.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%( '$1$2$1bonus.png"/>', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/portfolio.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/portfolio.hu.php new file mode 100644 index 0000000..ad01dad --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/portfolio.hu.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.portfolio.hu/gazdasag/mennybe-vagy-pokolba-megy-ma-a-cseh-trump.265833.html', + 'body' => array( + '//div[@id="cikk"]/h1', + '//div[@class="smscontent"]' + ), + 'strip' => array( + '//div[@class="traderhirdetes ga_viewanalytics"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pro-linux.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pro-linux.de.php new file mode 100644 index 0000000..bc76630 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/pro-linux.de.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.pro-linux.de/news/1/25252/chrome-62-erschienen.html', + 'body' => array( + '//div[@id="news"]', + ), + 'strip' => array( + '//h3[@class="topic"]', + '//h2[@class="title"]', + '//div[@class="picto"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publicpolicyforum.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publicpolicyforum.org.php new file mode 100644 index 0000000..5dc8be8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publicpolicyforum.org.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'https://publicpolicyforum.org/blog/going-extra-mile', + 'body' => array( + '//div[contains(@class,"field-name-post-date")]', + '//div[contains(@class,"field-name-body")]', + ), + 'strip' => array( + '//img[@src="http://publicpolicyforum.org/sites/default/files/logo3.jpg"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publy.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publy.ru.php new file mode 100644 index 0000000..bcfeeb9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/publy.ru.php @@ -0,0 +1,24 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.publy.ru/post/19988', + 'body' => array( + '//div[@class="singlepost"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[@class="featured"]', + '//*[@class="toc_white no_bullets"]', + '//*[@class="toc_title"]', + '//*[@class="pba"]', + '//*[@class="comments"]', + '//*[contains(@class, "g-single")]', + '//*[@class="ts-fab-wrapper"]', + '//*[contains(@class, "wp_rp_wrap")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/putaindecode.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/putaindecode.fr.php new file mode 100644 index 0000000..9fa5568 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/putaindecode.fr.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://putaindecode.fr/posts/js/etat-lieux-js-modulaire-front/', + 'body' => array( + '//*[@class="putainde-Post-md"]', + ), + 'strip' => array( + '//*[contains(@class, "inlineimg")]', + '//*[contains(@class, "comment-respond")]', + '//header', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/recode.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/recode.net.php new file mode 100644 index 0000000..343cd12 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/recode.net.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://recode.net/2015/09/26/big-tech-rolls-out-red-carpet-for-indian-prime-minister-lobbies-behind-closed-doors/', + 'body' => array( + '//img[contains(@class,"attachment-large")]', + '//div[contains(@class,"postarea")]', + '//li[@class,"author"]', + ), + 'strip' => array( + '//script', + '//div[contains(@class,"sharedaddy")]', + '//div[@class="post-send-off"]', + '//div[@class="large-12 columns"]', + '//div[contains(@class,"inner-related-article")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/retractionwatch.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/retractionwatch.com.php new file mode 100644 index 0000000..b97c73e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/retractionwatch.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://retractionwatch.com/2015/11/12/psychologist-jens-forster-settles-case-by-agreeing-to-2-retractions/', + 'body' => array( + '//*[@class="main"]', + '//*[@class="entry-content"]', + ), + 'strip' => array( + '//*[contains(@class, "sharedaddy")]', + '//*[contains(@class, "jp-relatedposts")]', + '//p[@class="p1"]', + ) + ) + ) +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rockpapershotgun.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rockpapershotgun.com.php new file mode 100644 index 0000000..7111078 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rockpapershotgun.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.rockpapershotgun.com/2016/08/26/the-divisions-expansions-delayed-to-improve-the-game/', + 'body' => array( + '//div[@class="entry"]', + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rue89.nouvelobs.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rue89.nouvelobs.com.php new file mode 100644 index 0000000..cb9116a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rue89.nouvelobs.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://rue89.feedsportal.com/c/33822/f/608948/s/30999fa0/sc/24/l/0L0Srue890N0C20A130C0A80C30A0Cfaisait0Eboris0Eboillon0Eex0Esarko0Eboy0E350A0E0A0A0A0Eeuros0Egare0Enord0E245315/story01.htm', + 'body' => array( + '//*[@id="article"]/div[contains(@class, "content")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rugbyrama.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rugbyrama.fr.php new file mode 100644 index 0000000..9915c23 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/rugbyrama.fr.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.rugbyrama.fr/rugby/top-14/2015-2016/top-14-hayman-coupe-du-monde-finale-2012-lutte.-voici-levan-chilachava-toulon_sto5283863/story.shtml', + 'body' => array( + '//div[@class="storyfull__content"]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[@class="share-buttons"]', + '//*[@class="ad"]', + '//*[@class="hide-desktop"]', + '//*[@id="tracking_img"]', + ) + ) + ) +); \ No newline at end of file diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/salonkolumnisten.com b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/salonkolumnisten.com new file mode 100644 index 0000000..37f43e9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/salonkolumnisten.com @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.salonkolumnisten.com/schulpolitik-niedersachsen/', + 'body' => array( + '//div[@id="main"]/div[contains(@class, "featimg")]', + '//div[@id="main"]/article/div[contains(@class, "entry-content")]', + ), + 'strip' => array( + '//div[@id="main"]/article/div[contains(@class, "entry-content")]/a[1]', + '//div[@id="main"]/article/div[contains(@class, "entry-content")]/a[1]', + ), + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/satwcomic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/satwcomic.com.php new file mode 100644 index 0000000..d63fc11 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/satwcomic.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://satwcomic.com/day-at-the-beach', + 'body' => array( + '//div[@class="container"]/center/a/img', + '//span[@itemprop="articleBody"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/science-skeptical.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/science-skeptical.de.php new file mode 100644 index 0000000..fcf045f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/science-skeptical.de.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.science-skeptical.de/politik/diesel-die-lueckenmedien-im-glashaus-6/0016080/', + 'body' => array( + '//div[@class="pf-content"]', + ), + 'strip' => array( + '//div[contains(@class, "printfriendly")]', + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/scrumalliance.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/scrumalliance.org.php new file mode 100644 index 0000000..7835fd9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/scrumalliance.org.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.scrumalliance.org/community/articles/2015/march/an-introduction-to-agile-project-intake?feed=articles', + 'body' => array( + '//div[@class="article_content"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/securityfocus.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/securityfocus.com.php new file mode 100644 index 0000000..0104514 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/securityfocus.com.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.securityfocus.com/archive/1/540139', + 'body' => array( + '//div[@id="vulnerability"]', + '//div[@class="comments_reply"]', + ), + 'strip' => array( + '//span[@class="title"]', + '//div[@id="logo_new"]', + '//div[@id="bannerAd"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sentfromthemoon.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sentfromthemoon.com.php new file mode 100644 index 0000000..f435417 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sentfromthemoon.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'body' => array( + '//div[@class="comicpane"]/a/img', + '//div[@class="entry"]', + ), + 'strip' => array(), + 'test_url' => 'http://sentfromthemoon.com/archives/1417', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sitepoint.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sitepoint.com.php new file mode 100644 index 0000000..ab0eb7d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sitepoint.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.sitepoint.com/creating-hello-world-app-swift/', + 'body' => array( + '//section[@class="article_body"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/slashdot.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/slashdot.org.php new file mode 100644 index 0000000..89ced8b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/slashdot.org.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://science.slashdot.org/story/15/04/20/0528253/pull-top-can-tabs-at-50-reach-historic-archaeological-status', + 'body' => array( + '//article/div[@class="body"] | //article[@class="layout-article"]/div[@class="elips"]', ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smallhousebliss.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smallhousebliss.com.php new file mode 100644 index 0000000..8c13c44 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smallhousebliss.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://smallhousebliss.com/2013/08/29/house-g-by-lode-architecture/', + 'body' => array( + '//div[@class="post-content"]', + ), + 'strip' => array( + '//*[contains(@class, "gallery")]', + '//*[contains(@class, "share")]', + '//*[contains(@class, "wpcnt")]', + '//*[contains(@class, "meta")]', + '//*[contains(@class, "postitle")]', + '//*[@id="nav-below"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smarthomewelt.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smarthomewelt.de.php new file mode 100644 index 0000000..7463abc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smarthomewelt.de.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://smarthomewelt.de/apple-tv-amazon-echo-smart-home/', + 'body' => array('//div[@class="entry-inner"]/p | //div[@class="entry-inner"]/div[contains(@class,"wp-caption")]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smashingmagazine.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smashingmagazine.com.php new file mode 100644 index 0000000..cbe1072 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smashingmagazine.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.smashingmagazine.com/2015/04/17/using-sketch-for-responsive-web-design-case-study/', + 'body' => array('//article[contains(@class,"post")]/p'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smbc-comics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smbc-comics.com.php new file mode 100644 index 0000000..42262dc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/smbc-comics.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.smbc-comics.com/comic/the-troll-toll', + 'body' => array( + '//div[@id="cc-comicbody"]', + '//div[@id="aftercomic"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/snopes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/snopes.com.php new file mode 100644 index 0000000..b0fe655 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/snopes.com.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.snopes.com/bacca-brides-on-tour/', + 'body' => array( + '//article', + ), + 'strip' => array( + '//span[@itemprop="author"]', + '//div[contains(@class,"author-")]', + '//h1', + '//style', + '//div[contains(@class,"socialShares")]', + '//div[contains(@class,"ad-unit")]', + '//aside', + '//div[contains(@class,"boomtrain")]', + '//footer' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/soundandvision.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/soundandvision.com.php new file mode 100644 index 0000000..6448bb0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/soundandvision.com.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.soundandvision.com/content/james-guthrie-mixing-roger-waters-and-pink-floyd-51', + 'body' => array( + '//div[@id="left"]', + ), + 'strip' => array( + '//div[@class="meta"]', + '//div[@class="ratingsbox"]', + '//h1', + '//h2', + '//addthis', + '//comment-links', + '//div[@class="book-navigation"]', + '//div[@class="comment-links"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/spiegel.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/spiegel.de.php new file mode 100644 index 0000000..b42c3aa --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/spiegel.de.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.spiegel.de/politik/ausland/afrika-angola-geht-gegen-islam-vor-und-schliesst-moscheen-a-935788.html', + 'body' => array( + '//div[@class="spArticleContent"]/p | //div[@class="spArticleContent"]//img[@class="spResponsiveImage "]', + ), + 'strip' => array( + '//div[@class="author-details"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stereophile.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stereophile.com.php new file mode 100644 index 0000000..8e410be --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stereophile.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.stereophile.com/content/2015-rocky-mountain-audio-fest-starts-friday', + 'body' => array( + '//div[@class="content clear-block"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stupidfox.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stupidfox.net.php new file mode 100644 index 0000000..61182d7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/stupidfox.net.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://stupidfox.net/134-sleepy-time', + 'body' => array( + '//div[@class="comicmid"]/center/a/img', + '//div[@class="stand_high"]', + ), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/subtraction.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/subtraction.com.php new file mode 100644 index 0000000..6d74427 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/subtraction.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.subtraction.com/2015/06/06/time-lapse-video-of-one-world-trade-center/', + 'body' => array('//article/div[@class="entry-content"]'), + 'strip' => array(), + ), + ), + 'filter' => array( + '%.*%' => array( + '%\+%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sz.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sz.de.php new file mode 100644 index 0000000..90bde5a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/sz.de.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://sz.de/1.2443161', + 'body' => array('//article[@id="sitecontent"]/section[@class="topenrichment"]//img | //article[@id="sitecontent"]/section[@class="body"]/section[@class="authors"]/preceding-sibling::*[not(contains(@class, "ad"))]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/takprosto.cc.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/takprosto.cc.php new file mode 100644 index 0000000..624ef90 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/takprosto.cc.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://takprosto.cc/kokteyl-dlya-pohudeniya-v-domashnih-usloviyah/', + 'body' => array( + '//div[contains(@class, "entry-contentt")]', + ), + 'strip' => array( + '//script', + '//form', + '//style', + '//*[@class="views_post"]', + '//*[contains(@class, "mailchimp-box")]', + '//*[contains(@class, "essb_links")]', + '//*[contains(@rel, "nofollow")]', + '//*[contains(@class, "ads")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/techcrunch.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/techcrunch.com.php new file mode 100644 index 0000000..230b791 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/techcrunch.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://techcrunch.com/2013/08/31/indias-visa-maze/', + 'body' => array( + '//div[contains(@class, "media-container")]', + '//div[contains(@class, "article-entry")]', + ), + 'strip' => array( + '//*[contains(@class, "module-crunchbase")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/the-ebook-reader.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/the-ebook-reader.com.php new file mode 100644 index 0000000..3b01eb9 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/the-ebook-reader.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://blog.the-ebook-reader.com/2015/09/25/kobo-glo-hd-and-kobo-touch-2-0-covers-and-cases-roundup/', + 'body' => array( + '//div[@class="entry"]', + ), + 'strip' => array( + '//div[@id="share"]', + '//div[contains(@class,"ItemCenter")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theatlantic.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theatlantic.com.php new file mode 100644 index 0000000..bfad4ab --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theatlantic.com.php @@ -0,0 +1,23 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.theatlantic.com/politics/archive/2015/09/what-does-it-mean-to-lament-the-poor-inside-panem/407317/', + 'body' => array( + '//picture[@class="img"]', + '//figure/figcaption/span', + '//div/p[@itemprop="description"]', + '//div[@class="article-body"]', + '//ul[@class="photos"]', + ), + 'strip' => array( + '//aside[@class="callout"]', + '//span[@class="credit"]', + '//figcaption[@class="credit"]', + '//aside[contains(@class,"partner-box")]', + '//div[contains(@class,"ad")]', + '//a[contains(@class,"social-icon")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theawkwardyeti.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theawkwardyeti.com.php new file mode 100644 index 0000000..fd4f3d5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theawkwardyeti.com.php @@ -0,0 +1,12 @@ + array( + '%/comic/.*%' => array( + 'test_url' => 'http://theawkwardyeti.com/comic/things-to-do/', + 'body' => array( + '//div[@id="comic"]' + ), + 'strip' => array() + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thecodinglove.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thecodinglove.com.php new file mode 100644 index 0000000..c6ec5bf --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thecodinglove.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://thecodinglove.com/post/116897934767', + 'body' => array('//div[@class="bodytype"]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thedoghousediaries.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thedoghousediaries.com.php new file mode 100644 index 0000000..d2f840d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thedoghousediaries.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'body' => array( + '//div[@class="comicpane"]/a/img', + '//div[@class="entry"]', + ), + 'strip' => array(), + 'test_url' => 'http://thedoghousediaries.com/6023', + ), + ), + 'filter' => array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thegamercat.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thegamercat.com.php new file mode 100644 index 0000000..f9b4637 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thegamercat.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.thegamercat.com/comic/just-no/', + 'body' => array('//div[@id="comic"] | //div[@class="post-content"]/div[@class="entry"]/p'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thehindu.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thehindu.com.php new file mode 100644 index 0000000..1e6735b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thehindu.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.thehindu.com/sci-tech/science/why-is-the-shape-of-cells-in-a-honeycomb-always-hexagonal/article7692306.ece?utm_source=RSS_Feed&utm_medium=RSS&utm_campaign=RSS_Syndication', + 'body' => array( + '//div/img[@class="main-image"]', + '//div[@class="photo-caption"]', + '//div[@class="articleLead"]', + '//p', + '//span[@class="upper"]', + ), + 'strip' => array( + '//div[@id="articleKeywords"]', + '//div[@class="photo-source"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thelocal.se.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thelocal.se.php new file mode 100644 index 0000000..c3ec250 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thelocal.se.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'www.thelocal.se/20161219/this-swede-can-memorize-hundreds-of-numbers-in-only-five-minutes', + 'body' => array( + '//div[@id="article-photo"]', + '//div[@id="article-description"]', + '//div[@id="article-body"]', + ), + 'strip' => array( + '//div[@id="article-info-middle"]', + ) + ) + ) +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themerepublic.net.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themerepublic.net.php new file mode 100644 index 0000000..bc47b27 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themerepublic.net.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.themerepublic.net/2015/04/david-lopez-pitoko.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+blogspot%2FDngUJ+%28Theme+Republic%29&utm_content=FeedBurner', + 'body' => array('//*[@class="post-body"]'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themoscowtimes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themoscowtimes.com.php new file mode 100644 index 0000000..0f5bf75 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/themoscowtimes.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.themoscowtimes.com/business/article/535500.html', + 'body' => array( + '//div[@class="article_main_img"]', + '//div[@class="article_text"]', + ), + 'strip' => array( + '//div[@class="articlebottom"]', + '//p/b', + '//p/a[contains(@href, "/article.php?id=")]', + '//div[@class="disqus_wrap"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thenewslens.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thenewslens.com.php new file mode 100644 index 0000000..7538170 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thenewslens.com.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://international.thenewslens.com/post/255032/', + 'body' => array( + '//div[@class="article-section"]', + ), + 'strip' => array( + '//div[contains(@class,"ad-")]', + '//div[@class="article-title-box"]', + '//div[@class="function-box"]', + '//p/span', + '//aside', + '//footer', + '//div[@class="article-infoBot-box"]', + '//div[contains(@class,"standard-container")]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theodd1sout.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theodd1sout.com.php new file mode 100644 index 0000000..d06ed12 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theodd1sout.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%-150x150%' => '', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theonion.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theonion.com.php new file mode 100644 index 0000000..acbfd36 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theonion.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.theonion.com/article/wild-eyed-jim-harbaugh-informs-players-they-must-k-51397?utm_medium=RSS&utm_campaign=feeds', + 'body' => array( + '//div[@class="content-masthead"]/figure/div/noscript/img', + '//div[@class="content-text"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theregister.co.uk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theregister.co.uk.php new file mode 100644 index 0000000..896365a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theregister.co.uk.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.theregister.co.uk/2017/10/21/purism_cleanses_laptops_of_intel_management_engine/', + 'body' => array( + '//div[@id="article"]', + ), + 'strip' => array( + '//div[@class="byline_and_share"]', + '//div[@class="social_btns alt_colour dcl"]', + '//div[@class="promo_article"]', + '//div[@id="article_body_btm"]', + '//p[@class="wptl btm"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thestandard.com.hk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thestandard.com.hk.php new file mode 100644 index 0000000..1163b34 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/thestandard.com.hk.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.thestandard.com.hk/breaking_news_detail.asp?id=67156', + 'body' => array( + '//table/tr/td/span[@class="bodyCopy"]', + ), + 'strip' => array( + '//script', + '//br', + '//map[@name="gif_bar"]', + '//img[contains(@usemap,"gif_bar")]', + '//a', + '//span[@class="bodyHeadline"]', + '//i', + '//b', + '//table', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theverge.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theverge.com.php new file mode 100644 index 0000000..09e876d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/theverge.com.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.theverge.com/2017/11/11/16624298/mindhunter-netflix-show-david-fincher-review', + 'body' => array( + '//figure[@class="e-image e-image--hero"]/span[@class="e-image__inner"]', + '//div[@class="c-entry-content"]', + ), + 'strip' => array( + '//div[@class="c-related-list"]', + '//div[@class="c-page-title"]', + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/threepanelsoul.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/threepanelsoul.com.php new file mode 100644 index 0000000..4af6196 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/threepanelsoul.com.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'body' => array( + '//img[@id="cc-comic"]', + ), + 'test_url' => 'http://www.threepanelsoul.com/comic/uncloaking', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tichyseinblick.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tichyseinblick.de.php new file mode 100644 index 0000000..6fba3df --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tichyseinblick.de.php @@ -0,0 +1,22 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.tichyseinblick.de/daili-es-sentials/jamaika-reaktionen-der-enttaeuschten/', + 'body' => array( + '//article' + ), + 'strip' => array( + '//header', + '//footer', + '//div[@class="mod-cad2"]', + '//ul[contains(@class, "social")]', + '//div[@class="rty-pop-up"]', + '//div[@class="pagelink"]', + '//div[@id="reward"]', + '//div[@class="rty-block-plista"]' + ) + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/timesofindia.indiatimes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/timesofindia.indiatimes.com.php new file mode 100644 index 0000000..1924680 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/timesofindia.indiatimes.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://timesofindia.indiatimes.com/city/mangaluru/Adani-UPCL-to-release-CSR-grant-of-Rs-3-74-crore-to-YellurGram-Panchayat/articleshow/50512116.cms', + 'body' => array( + '//div[@class="article_content clearfix"]', + '//section[@class="highlight clearfix"]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/totalcar.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/totalcar.hu.php new file mode 100644 index 0000000..f2e009d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/totalcar.hu.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://totalcar.hu/tesztek/2017/10/21/veteran_fiat-abarth_1000tc_1968/', + 'body' => array( + '//div[@class="content-title"]', + '//div[@class="lead-container"]/div[@class="lead"]', + '//div[@class="cikk-torzs"]' + ), + 'strip' => array( + '//span[@class="gallery_title newline"]', + '//div[@class="social-stripe cikk-bottom-box"]', + '//div[@class="cikk-bottom-text-ad"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tozsdeforum.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tozsdeforum.hu.php new file mode 100644 index 0000000..97a0da0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tozsdeforum.hu.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.tozsdeforum.hu/szemelyes-penzugyek/napi-penzugyek/ezek-a-legnepszerubb-turistacelpontok-voltal-mar-mindenhol-87181.html', + 'body' => array( + '//header/h1', + '//div[@class="title_img"]', + '//article/div[@class="tf-post"]/div[@class="p"]/p|//article/div[@class="tf-post"]/div[@class="p"]/h3|//article/div[@class="tf-post"]/div[@class="p"]/blockquote' + ), + 'strip' => array( + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travel-dealz.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travel-dealz.de.php new file mode 100644 index 0000000..4ee4fcd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travel-dealz.de.php @@ -0,0 +1,15 @@ + array( + '%^/blog.*%' => array( + 'test_url' => 'http://travel-dealz.de/blog/venere-gutschein/', + 'body' => array('//div[@class="post-entry"]'), + 'strip' => array( + '//*[@id="jp-relatedposts"]', + '//*[@class="post-meta"]', + '//*[@class="post-data"]', + '//*[@id="author-meta"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travelo.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travelo.hu.php new file mode 100644 index 0000000..3ab8ca4 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/travelo.hu.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://travelo.hu/csaladbarat/2017/10/20/mar_csak_egy_het_es_kezdodik_az_oszi_szunet/', + 'body' => array( + '//div[@id="content"]/h1', + '//div[@id="kopf"]', + '//div[@id="szoveg"]' + ), + 'strip' => array( + '//div[@class="goAdverticum"]', + '//h1[@class="border"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treehugger.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treehugger.com.php new file mode 100644 index 0000000..55eb7e0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treehugger.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.treehugger.com/uncategorized/top-ten-posts-week-bunnies-2.html', + 'body' => array( + '//div[contains(@class, "promo-image")]', + '//div[contains(@id, "entry-body")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treelobsters.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treelobsters.com.php new file mode 100644 index 0000000..3214c62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/treelobsters.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%title="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tutorialzine.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tutorialzine.com.php new file mode 100644 index 0000000..7e39145 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/tutorialzine.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'https://tutorialzine.com/2017/10/15-interesting-javascript-and-css-libraries-for-october-2017', + 'body' => array( + '//article' + ), + 'strip' => array( + '//div[@class="article__header"]', + '//div[@class="article__share"]', + '//div[@class="article__footer"]', + '//div[@id="article__related-articles"]', + '//div[@class="webappstudio-animation"]', + '//div[@class="ad-container adsbygoogle hidden-xs hidden-sm"]', + '//script' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twogag.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twogag.com.php new file mode 100644 index 0000000..79f4f62 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twogag.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%http://www.twogag.com/comics-rss/([^.]+)\\.jpg%' => 'http://www.twogag.com/comics/$1.jpg', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twokinds.keenspot.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twokinds.keenspot.com.php new file mode 100644 index 0000000..3428fcb --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/twokinds.keenspot.com.php @@ -0,0 +1,10 @@ + array( + '%.*%' => array( + 'test_url' => 'http://twokinds.keenspot.com/archive.php?p=0', + 'body' => array('//*[@class="comic"]/div/a/img | //*[@class="comic"]/div/img | //*[@id="cg_img"]/img | //*[@id="cg_img"]/a/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/undeadly.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/undeadly.org.php new file mode 100644 index 0000000..8b15900 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/undeadly.org.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://undeadly.org/cgi?action=article&sid=20141101181155', + 'body' => array( + '/html/body/table[3]/tbody/tr/td[1]/table[2]/tr/td[1]', + ), + 'strip' => array( + '//font', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/upi.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/upi.com.php new file mode 100644 index 0000000..ec8d1a1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/upi.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.upi.com/Top_News/US/2015/09/26/Tech-giants-Hollywood-stars-among-guests-at-state-dinner-for-Chinas-Xi-Jinping/4541443281006/', + 'body' => array( + '//div[@class="img"]', + '//div/article[@itemprop="articleBody"]', + ), + 'strip' => array( + '//div[@align="center"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/usatoday.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/usatoday.com.php new file mode 100644 index 0000000..edd6aa4 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/usatoday.com.php @@ -0,0 +1,27 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.usatoday.com/story/life/music/2017/02/13/things-you-should-know-happened-grammy-awards-2017/97833734/', + 'body' => array( + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//script', + '//h1', + '//iframe', + '//span[@class="mycapture-small-btn mycapture-btn-with-text mycapture-expandable-photo-btn-small js-mycapture-btn-small"]', + '//div[@class="close-wrap"]', + '//div[contains(@class,"ui-video-wrapper")]', + '//div[contains(@class,"media-mob")]', + '//div[contains(@class,"left-mob")]', + '//div[contains(@class,"nerdbox")]', + '//div[contains(@class,"oembed-asset")]', + '//*[contains(@class,"share")]', + '//div[contains(@class,"gallery-asset")]', + '//div[contains(@class,"oembed-asset")]', + '//div[@class="article-print-url"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/version2.dk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/version2.dk.php new file mode 100644 index 0000000..a6d49f2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/version2.dk.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.version2.dk/artikel/surface-pro-2-fungerer-bedre-til-arbejde-end-fornoejelse-55195', + 'body' => array( + '//section[contains(@class, "teaser")]', + '//section[contains(@class, "body")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vezess.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vezess.hu.php new file mode 100644 index 0000000..acd68c0 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vezess.hu.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.vezess.hu/hirek/2017/10/20/audi-a7-2018-bemutato/', + 'body' => array( + '//article[@id="news"]/h1', + '//article[@id="news"]/h2', + '//article[@id="news"]/p[@class="lead"]', + '//article[@id="news"]/p[@class="main-pic responsive-img-container"]', + '//div[@class="article-body"]' + ), + 'strip' => array( + '//div[@class="info-bar"]', + '//ul[@class="breadcrumb"]', + '//div[@class="embed-link ce_widget"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vgcats.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vgcats.com.php new file mode 100644 index 0000000..b2830a3 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vgcats.com.php @@ -0,0 +1,15 @@ + array( + '%/comics.*%' => array( + 'test_url' => 'http://www.vgcats.com/comics/?strip_id=358', + 'body' => array('//*[@align="center"]/img'), + 'strip' => array(), + ), + '%/super.*%' => array( + 'test_url' => 'http://www.vgcats.com/super/?strip_id=84', + 'body' => array('//*[@align="center"]/p/img'), + 'strip' => array(), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vuxml.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vuxml.org.php new file mode 100644 index 0000000..b9bef7a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/vuxml.org.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.vuxml.org/freebsd/a5f160fa-deee-11e4-99f8-080027ef73ec.html', + 'body' => array( + '//body', + ), + 'strip' => array( + '//h1', + '//div[@class="blurb"]', + '//hr', + '//p[@class="copyright"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/wausaudailyherald.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/wausaudailyherald.com.php new file mode 100644 index 0000000..58aceea --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/wausaudailyherald.com.php @@ -0,0 +1,27 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.wausaudailyherald.com/story/news/2017/04/01/hundreds-gather-remember-attorney-killed-shooting-spree/99826062/?from=global&sessionKey=&autologin=', + 'body' => array( + '//div[@itemprop="articleBody"]', + ), + 'strip' => array( + '//h1', + '//iframe', + '//span[@class="mycapture-small-btn mycapture-btn-with-text mycapture-expandable-photo-btn-small js-mycapture-btn-small"]', + '//div[@class="close-wrap"]', + '//div[contains(@class,"ui-video-wrapper")]', + '//div[contains(@class,"media-mob")]', + '//div[contains(@class,"left-mob")]', + '//div[contains(@class,"nerdbox")]', + '//p/span', + '//div[contains(@class,"oembed-asset")]', + '//*[contains(@class,"share")]', + '//div[contains(@class,"gallery-asset")]', + '//div[contains(@class,"oembed-asset")]', + '//div[@class="article-print-url"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/welt.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/welt.de.php new file mode 100644 index 0000000..83a2ebe --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/welt.de.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'https://www.welt.de/debatte/kommentare/article169740590/Bloss-keine-sozialdemokratische-Konsenssause.html', + 'body' => array( + '//main/article/header/div/div[contains(@class, "c-summary")]/div', + '//main/article/header/div[3]/div/figure/div/div/div/picture[1]', + '//main/article/header/div[3]/div/figure/figcaption/child::*', + '//main/article/div[contains(@class, "c-article-text")]' + ), + 'strip' => array( + '//*[contains(@class, "c-inline-element--has-commercials")]', + '//*[contains(@class, "c-inline-teaser")]', + '//figure[contains(@class, "c-video-element")]', + '//main/article/div[contains(@class, "c-article-text")]/div[@class="c-inline-element"]/div[contains(@class, "c-image-element")]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/westfalen-blatt.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/westfalen-blatt.de.php new file mode 100644 index 0000000..adcf417 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/westfalen-blatt.de.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.westfalen-blatt.de/OWL/Lokales/Kreis-Hoexter/Warburg/3024113-Polizei-in-Warburg-Hier-waren-keine-kriminellen-Profis-am-Werk-Wurstautomat-Sprengung-mit-Polen-Boellern', + 'body' => array( + '//div[contains(@class, "articleimage")]', + '//div[@class="attribute-short"]', + '//div[@class="attribute-long"]', + ), + 'strip' => array( + '//div[@class="fb-post"]' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bbc.co.uk.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bbc.co.uk.php new file mode 100644 index 0000000..98fc368 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bbc.co.uk.php @@ -0,0 +1,33 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bbc.co.uk/news/world-middle-east-23911833', + 'body' => array( + '//div[@class="story-body__inner"] | //div[@class="article"]', + '//div[@class="indPost"]', + ), + 'strip' => array( + '//form', + '//div[@id="headline"]', + '//*[@class="warning"]', + '//span[@class="off-screen"]', + '//span[@class="story-image-copyright"]', + '//ul[@class="story-body__unordered-list"]', + '//div[@class="ad_wrapper"]', + '//div[@id="article-sidebar"]', + '//div[@class="data-table-outer"]', + '//*[@class="story-date"]', + '//*[@class="story-header"]', + '//figure[contains(@class,"has-caption")]', + '//*[@class="story-related"]', + '//*[contains(@class, "byline")]', + '//p[contains(@class, "media-message")]', + '//*[contains(@class, "story-feature")]', + '//*[@id="video-carousel-container"]', + '//*[@id="also-related-links"]', + '//*[contains(@class, "share") or contains(@class, "hidden") or contains(@class, "hyper")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bdgest.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bdgest.com.php new file mode 100644 index 0000000..41ef68d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bdgest.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bdgest.com/chronique-6027-BD-Adrastee-Tome-2.html', + 'body' => array( + '//*[contains(@class, "chronique")]', + ), + 'strip' => array( + '//*[contains(@class, "post-review")]', + '//*[contains(@class, "footer-review")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bgr.in.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bgr.in.php new file mode 100644 index 0000000..63ca069 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.bgr.in.php @@ -0,0 +1,23 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.bgr.in/news/xiaomi-redmi-3-with-13-megapixel-camera-snapdragon-616-launched-price-specifications-and-features/', + 'body' => array( + '//div[@class="article-content"]', + ), + 'strip' => array( + '//*[@class="article-meta"]', + '//*[@class="contentAdsense300"]', + '//*[@class="iwpl-social-hide"]', + '//iframe[@class="iframeads"]', + '//*[@class="disqus_thread"]', + '//*[@class="outb-mobile OUTBRAIN"]', + '//*[@class="wdt_smart_alerts"]', + '//*[@class="footnote"]', + '//*[@id="gadget-widget"]', + '//header[@class="article-title entry-header"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.businessweek.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.businessweek.com.php new file mode 100644 index 0000000..0acc44e --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.businessweek.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.businessweek.com/articles/2013-09-18/elon-musks-hyperloop-will-work-says-some-very-smart-software', + 'body' => array( + '//div[@id="lead_graphic"]', + '//div[@id="article_body"]', + ), + 'strip' => array( + '//*[contains(@class, "related_item")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.cnn.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.cnn.com.php new file mode 100644 index 0000000..31d03ed --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.cnn.com.php @@ -0,0 +1,24 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.cnn.com/2013/08/31/world/meast/syria-civil-war/index.html?hpt=hp_t1', + 'body' => array( + '//div[@class="cnn_strycntntlft"]', + ), + 'strip' => array( + '//div[@class="cnn_stryshrwdgtbtm"]', + '//div[@class="cnn_strybtmcntnt"]', + '//div[@class="cnn_strylftcntnt"]', + '//div[contains(@class, "cnnGalleryContainer")]', + '//div[contains(@class, "cnn_strylftcexpbx")]', + '//div[contains(@class, "articleGalleryNavContainer")]', + '//div[contains(@class, "cnnArticleGalleryCaptionControl")]', + '//div[contains(@class, "cnnArticleGalleryNavPrevNextDisabled")]', + '//div[contains(@class, "cnnArticleGalleryNavPrevNext")]', + '//div[contains(@class, "cnn_html_media_title_new")]', + '//div[contains(@id, "disqus")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.developpez.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.developpez.com.php new file mode 100644 index 0000000..1535e43 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.developpez.com.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.developpez.com/actu/81757/Mozilla-annonce-la-disponibilite-de-Firefox-36-qui-passe-au-HTTP-2-et-permet-la-synchronisation-de-son-ecran-d-accueil/', + 'body' => array( + '//*[@itemprop="articleBody"]', + ), + 'strip' => array( + '//form', + '//div[@class="content"]/img', + '//a[last()]/following-sibling::*', + '//*[contains(@class,"actuTitle")]', + '//*[contains(@class,"date")]', + '//*[contains(@class,"inlineimg")]', + '//*[@id="signaler"]', + '//*[@id="signalerFrame"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.egscomics.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.egscomics.com.php new file mode 100644 index 0000000..263f075 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.egscomics.com.php @@ -0,0 +1,12 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.egscomics.com/index.php?id=1690', + 'title' => '/html/head/title', + 'body' => array( + '//img[@id="comic"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.fakingnews.firstpost.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.fakingnews.firstpost.com.php new file mode 100644 index 0000000..c948c77 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.fakingnews.firstpost.com.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.fakingnews.firstpost.com/2016/01/engineering-student-creates-record-in-a-decade-becomes-the-first-to-completely-exhaust-ball-pen-refill/', + 'body' => array( + '//div[@class="entry"]', + ), + 'strip' => array( + '//*[@class="socialshare_bar"]', + '//*[@class="authorbox"]', + '//*[@class="cf5_rps"]', + '//*[@class="60563 fb-comments fb-social-plugin"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.forbes.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.forbes.com.php new file mode 100644 index 0000000..fd16ed5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.forbes.com.php @@ -0,0 +1,20 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.forbes.com/sites/andygreenberg/2013/09/05/follow-the-bitcoins-how-we-got-busted-buying-drugs-on-silk-roads-black-market/', + 'body' => array( + '//div[@id="leftRail"]/div[contains(@class, body)]', + ), + 'strip' => array( + '//aside', + '//div[contains(@class, "entity_block")]', + '//div[contains(@class, "vestpocket") and not contains(@class, "body")]', + '//div[contains(@style, "display")]', + '//div[contains(@id, "comment")]', + '//div[contains(@class, "widget")]', + '//div[contains(@class, "pagination")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.franceculture.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.franceculture.fr.php new file mode 100644 index 0000000..f7ec0d8 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.franceculture.fr.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.franceculture.fr/emission-culture-eco-la-finance-aime-toujours-la-france-2016-01-08', + 'body' => array( + '//div[@class="text-zone"]', + ), + 'strip' => array( + '//ul[@class="tags"]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.futura-sciences.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.futura-sciences.com.php new file mode 100644 index 0000000..ea94a0f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.futura-sciences.com.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.futura-sciences.com/magazines/espace/infos/actu/d/astronautique-curiosity-franchi-succes-dune-dingo-gap-52289/#xtor=RSS-8', + 'body' => array( + '//div[contains(@class, "content fiche-")]', + ), + 'strip' => array( + '//h1', + '//*[contains(@class, "content-date")]', + '//*[contains(@class, "diaporama")]', + '//*[contains(@class, "slider")]', + '//*[contains(@class, "cartouche")]', + '//*[contains(@class, "noprint")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.geekculture.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.geekculture.com.php new file mode 100644 index 0000000..3d0b6c7 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.geekculture.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.geekculture.com/joyoftech/joyarchives/2180.html', + 'body' => array( + '//p[contains(@class,"Maintext")][2]/a/img[contains(@src,"joyimages")]', + ), + 'strip' => array(), + ), + ), +); + diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.howtogeek.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.howtogeek.com.php new file mode 100644 index 0000000..6879e76 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.howtogeek.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.howtogeek.com/235283/what-is-a-wireless-hard-drive-and-should-i-get-one/', + 'body' => array( + '//div[@class="thecontent"]', + ), + 'strip' => array( + '//*[@class="relatedside"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lepoint.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lepoint.fr.php new file mode 100644 index 0000000..dcb7e48 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lepoint.fr.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.lepoint.fr/c-est-arrive-aujourd-hui/19-septembre-1783-pour-la-premiere-fois-un-mouton-un-canard-et-un-coq-s-envoient-en-l-air-devant-louis-xvi-18-09-2012-1507704_494.php', + 'body' => array( + '//article', + ), + 'strip' => array( + '//*[contains(@class, "info_article")]', + '//*[contains(@class, "fildariane_titre")]', + '//*[contains(@class, "entete2_article")]', + '//*[contains(@class, "signature_article")]', + '//*[contains(@id, "share")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lesnumeriques.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lesnumeriques.com.php new file mode 100644 index 0000000..0137e20 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.lesnumeriques.com.php @@ -0,0 +1,25 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.lesnumeriques.com/blender/kitchenaid-diamond-5ksb1585-p27473/test.html', + 'body' => array( + '//*[@id="product-content"]', + '//*[@id="news-content"]', + '//*[@id="article-content"]', + ), + 'strip' => array( + '//form', + '//div[contains(@class, "price-v4"])', + '//div[contains(@class, "authors-and-date")]', + '//div[contains(@class, "mini-product")]', + '//div[@id="articles-related-authors"]', + '//div[@id="tags-socials"]', + '//div[@id="user-reviews"]', + '//div[@id="product-reviews"]', + '//div[@id="publication-breadcrumbs-and-date"]', + '//div[@id="publication-breadcrumbs-and-date"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.mac4ever.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.mac4ever.com.php new file mode 100644 index 0000000..60bc1bd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.mac4ever.com.php @@ -0,0 +1,13 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.mac4ever.com/actu/87392_video-quand-steve-jobs-et-bill-gates-jouaient-au-bachelor-avec-le-mac', + 'body' => array( + '//div[contains(@class, "news-news-content")]', + ), + 'strip' => array( + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.makeuseof.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.makeuseof.com.php new file mode 100644 index 0000000..a274564 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.makeuseof.com.php @@ -0,0 +1,18 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.makeuseof.com/tag/having-problems-with-audio-in-windows-10-heres-a-likely-fix/', + 'body' => array( + '//div[@class="entry"]', + ), + 'strip' => array( + '//*[@class="new_sharebar"]', + '//*[@class="author"]', + '//*[@class="wdt_grouvi"]', + '//*[@class="wdt_smart_alerts"]', + '//*[@class="modal fade grouvi"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.monsieur-le-chien.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.monsieur-le-chien.fr.php new file mode 100644 index 0000000..5f5e987 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.monsieur-le-chien.fr.php @@ -0,0 +1,11 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.monsieur-le-chien.fr/index.php?planche=672', + 'body' => array( + '//img[starts-with(@src, "i/planches/")]', + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.npr.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.npr.org.php new file mode 100644 index 0000000..ecc0213 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.npr.org.php @@ -0,0 +1,28 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.npr.org/blogs/thesalt/2013/09/17/223345977/auto-brewery-syndrome-apparently-you-can-make-beer-in-your-gut', + 'body' => array( + '//article[contains(@class,"story")]', + ), + 'strip' => array( + '//div[@class="story-tools"]', + '//h3[@class="slug"]', + '//div[@class="storytitle"]', + '//div[@id="story-meta"]', + '//a[@id="mainContent"]', + '//div[@class="credit-caption"]', + '//div[@class="enlarge_html"]', + '//button', + '//div[contains(@id,"pullquote")]', + '//div[contains(@class,"internallink")]', + '//div[contains(@class,"video")]', + '//div[@class="simplenodate"]', + '//div[contains(@class,"share-")]', + '//div[@class="tags"]', + '//aside' + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.numerama.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.numerama.com.php new file mode 100644 index 0000000..fe4971c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.numerama.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.numerama.com/sciences/125959-recherches-ladn-recompensees-nobel-de-chimie.html', + 'body' => array( + '//article', + ), + 'strip' => array( + '//footer', + '//section[@class="related-article"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.oneindia.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.oneindia.com.php new file mode 100644 index 0000000..320c214 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.oneindia.com.php @@ -0,0 +1,14 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.oneindia.com/india/b-luru-govt-likely-remove-word-eunuch-from-sec-36-a-karnataka-police-act-1981173.html', + 'body' => array( + '//div[@class="ecom-ad-content"]', + ), + 'strip' => array( + '//*[@id="view_cmtns"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.pseudo-sciences.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.pseudo-sciences.org.php new file mode 100644 index 0000000..9e467ed --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.pseudo-sciences.org.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.pseudo-sciences.org/spip.php?article2275', + 'body' => array( + '//div[@id="art_main"]', + ), + 'strip' => array( + '//div[@id="art_print"]', + '//div[@id="art_chapo"]', + '//img[@class="puce"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.sciencemag.org.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.sciencemag.org.php new file mode 100644 index 0000000..ae7a93a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.sciencemag.org.php @@ -0,0 +1,16 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.sciencemag.org/news/2016/01/could-bright-foamy-wak$', + 'body' => array( + '//div[@class="row--hero"]', + '//article[contains(@class,"primary")]', + ), + 'strip' => array( + '//header[@class="article__header"]', + '//footer[@class="article__foot"]', + ), + ), + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.slate.fr.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.slate.fr.php new file mode 100644 index 0000000..8c8dc89 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.slate.fr.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.slate.fr/monde/77034/allemagne-2013-couacs-campagne', + 'body' => array( + '//div[@class="article_content"]', + ), + 'strip' => array( + '//*[@id="slate_associated_bn"]', + '//*[@id="ligatus-article"]', + '//*[@id="article_sidebar"]', + '//div[contains(@id, "reseaux")]', + '//*[contains(@class, "smart") or contains(@class, "article_tags") or contains(@class, "article_reactions")]', + '//*[contains(@class, "OUTBRAIN") or contains(@class, "related_item") or contains(@class, "share")]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.universfreebox.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.universfreebox.com.php new file mode 100644 index 0000000..0747d0f --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.universfreebox.com.php @@ -0,0 +1,15 @@ + array( + '%.*%' => array( + 'test_url' => 'http://www.universfreebox.com/article/24305/4G-Bouygues-Telecom-lance-une-vente-flash-sur-son-forfait-Sensation-3Go', + 'body' => array( + '//div[@id="corps_corps"]', + ), + 'strip' => array( + '//*[@id="formulaire"]', + '//*[@id="commentaire"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.zeit.de.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.zeit.de.php new file mode 100644 index 0000000..316c265 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/www.zeit.de.php @@ -0,0 +1,41 @@ + array( + '%^/zeit-magazin.*%' => array( + 'test_url' => 'http://www.zeit.de/zeit-magazin/2015/15/pegida-kathrin-oertel-lutz-bachmann', + 'body' => array( + '//article[@class="article"]', + ), + 'strip' => array( + '//header/div/h1', + '//header/div/div[@class="article__head__subtitle"]', + '//header/div/div[@class="article__column__author"]', + '//header/div/div[@class="article__column__author"]', + '//header/div/span[@class="article__head__meta-wrap"]', + '//form', + '//style', + '//div[contains(@class, "ad-tile")]', + '//div[@class="iqd-mobile-adplace"]', + '//div[@id="iq-artikelanker"]', + '//div[@id="js-social-services"]', + '//section[@id="js-comments"]', + '//aside', + ), + ), + '%.*%' => array( + 'test_url' => 'http://www.zeit.de/politik/ausland/2015-04/thessaloniki-krise-griechenland-yannis-boutaris/', + 'body' => array( + '//div[@class="article-body"]', + ), + 'strip' => array( + '//*[@class="articleheader"]', + '//*[@class="excerpt"]', + '//div[contains(@class, "ad")]', + '//div[@itemprop="video"]', + '//*[@class="articlemeta"]', + '//*[@class="articlemeta-clear"]', + '//*[@class="zol_inarticletools"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/xkcd.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/xkcd.com.php new file mode 100644 index 0000000..8495726 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/xkcd.com.php @@ -0,0 +1,8 @@ + array( + '%.*%' => array( + '%alt="(.+)" */>%' => '/>
$1', + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ymatuhin.ru.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ymatuhin.ru.php new file mode 100644 index 0000000..9fd83f1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/ymatuhin.ru.php @@ -0,0 +1,21 @@ + array( + '%.*%' => array( + 'test_url' => 'https://ymatuhin.ru/tools/git-default-editor/', + 'body' => array( + '//section', + ), + 'strip' => array( + "//script", + "//style", + "//h1", + "//time", + "//aside", + "/html/body/section/ul", + "//amp-iframe", + "/html/body/section/h4" + ), + ) + ) +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zarojel.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zarojel.hu.php new file mode 100644 index 0000000..36d3bdf --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zarojel.hu.php @@ -0,0 +1,19 @@ + array( + '%.*%' => array( + 'test_url' => 'https://zarojel.hu/meg-egyszer-a-foldi-ugyrol-is/', + 'body' => array( + '//div[@class="entry-category"]/h1', + '//div[@class="entry-content"]/div[@class="vc_row wpb_row vc_row-fluid"]' + ), + 'strip' => array( + '//ins[@class="adsbygoogle"]', + '//script', + '//figcaption', + '//p[contains(text(),"Kapcsolódó")]', + '//div[@class="wpb_wrapper"]/p[@class="entry-title"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zdnet.com.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zdnet.com.php new file mode 100644 index 0000000..79b35dd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zdnet.com.php @@ -0,0 +1,23 @@ + array( + '%.*%' => array( + 'test_url' => 'http://zdnet.com.feedsportal.com/c/35462/f/675637/s/4a33c93e/sc/11/l/0L0Szdnet0N0Carticle0Cchina0Eus0Eagree0Eon0Ecybercrime0Ecooperation0Eamid0Econtinued0Etension0C0Tftag0FRSSbaffb68/story01.htm', + 'body' => array( + '//p[@class="summary"]', + '//div[contains(@class,"storyBody")]', + ), + 'strip' => array( + '//*[contains(@class,"ad-")]', + '//p/span', + '//script', + '//p[@class="summary"]', + '//div[contains(@class,"relatedContent")]', + '//div[contains(@class,"loader")]', + '//p[@class="photoDetails"]', + '//div[@class="thumbnailSlider"]', + '//div[@class="shortcodeGalleryWrapper"]', + ), + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zoom.hu.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zoom.hu.php new file mode 100644 index 0000000..3e38781 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Rules/zoom.hu.php @@ -0,0 +1,17 @@ + array( + '%.*%' => array( + 'test_url' => 'https://zoom.hu/2017/10/20/mar-nem-nyomoznak-a-vegrehajtok-botranyai-miatt', + 'body' => array( + '//div[@class="title-wrapper"]/h1', + '//div[@class="entry-excerpt"]', + '//div[@class="thumbnail-wrapper"]', + '//div[@id="entry-content-id"]' + ), + 'strip' => array( + '//div[@class="place first normal"]' + ) + ), + ), +); diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/CandidateParser.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/CandidateParser.php new file mode 100644 index 0000000..0f74b3d --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/CandidateParser.php @@ -0,0 +1,281 @@ +dom = XmlParser::getHtmlDocument(''.$html); + $this->xpath = new DOMXPath($this->dom); + } + + /** + * Get the relevant content with the list of potential attributes. + * + * @return string + */ + public function execute() + { + $content = $this->findContentWithCandidates(); + + if (strlen($content) < 200) { + $content = $this->findContentWithArticle(); + } + + if (strlen($content) < 50) { + $content = $this->findContentWithBody(); + } + + return $this->stripGarbage($content); + } + + /** + * Find content based on the list of tag candidates. + * + * @return string + */ + public function findContentWithCandidates() + { + foreach ($this->candidatesAttributes as $candidate) { + Logger::setMessage(get_called_class().': Try this candidate: "'.$candidate.'"'); + + $nodes = $this->xpath->query('//*[(contains(@class, "'.$candidate.'") or @id="'.$candidate.'") and not (contains(@class, "nav") or contains(@class, "page"))]'); + + if ($nodes !== false && $nodes->length > 0) { + Logger::setMessage(get_called_class().': Find candidate "'.$candidate.'"'); + + return $this->dom->saveXML($nodes->item(0)); + } + } + + return ''; + } + + /** + * Find
tag. + * + * @return string + */ + public function findContentWithArticle() + { + $nodes = $this->xpath->query('//article'); + + if ($nodes !== false && $nodes->length > 0) { + Logger::setMessage(get_called_class().': Find
tag'); + + return $this->dom->saveXML($nodes->item(0)); + } + + return ''; + } + + /** + * Find tag. + * + * @return string + */ + public function findContentWithBody() + { + $nodes = $this->xpath->query('//body'); + + if ($nodes !== false && $nodes->length > 0) { + Logger::setMessage(get_called_class().' Find '); + + return $this->dom->saveXML($nodes->item(0)); + } + + return ''; + } + + /** + * Strip useless tags. + * + * @param string $content + * @return string + */ + public function stripGarbage($content) + { + $dom = XmlParser::getDomDocument($content); + + if ($dom !== false) { + $xpath = new DOMXPath($dom); + + $this->stripTags($xpath); + $this->stripAttributes($dom, $xpath); + + $content = $dom->saveXML($dom->documentElement); + } + + return $content; + } + + /** + * Remove blacklisted tags. + * + * @param DOMXPath $xpath + */ + public function stripTags(DOMXPath $xpath) + { + foreach ($this->stripTags as $tag) { + $nodes = $xpath->query('//'.$tag); + + if ($nodes !== false && $nodes->length > 0) { + Logger::setMessage(get_called_class().': Strip tag: "'.$tag.'"'); + + foreach ($nodes as $node) { + $node->parentNode->removeChild($node); + } + } + } + } + + /** + * Remove blacklisted attributes. + * + * @param DomDocument $dom + * @param DOMXPath $xpath + */ + public function stripAttributes(DomDocument $dom, DOMXPath $xpath) + { + foreach ($this->stripAttributes as $attribute) { + $nodes = $xpath->query('//*[contains(@class, "'.$attribute.'") or contains(@id, "'.$attribute.'")]'); + + if ($nodes !== false && $nodes->length > 0) { + Logger::setMessage(get_called_class().': Strip attribute: "'.$attribute.'"'); + + foreach ($nodes as $node) { + if ($this->shouldRemove($dom, $node)) { + $node->parentNode->removeChild($node); + } + } + } + } + } + + /** + * Find link for next page of the article. + * + * @return string + */ + public function findNextLink() + { + return null; + } + + /** + * Return false if the node should not be removed. + * + * @param DomDocument $dom + * @param \DomNode $node + * @return bool + */ + public function shouldRemove(DomDocument $dom, $node) + { + $document_length = strlen($dom->textContent); + $node_length = strlen($node->textContent); + + if ($document_length === 0) { + return true; + } + + $ratio = $node_length * 100 / $document_length; + + if ($ratio >= 90) { + Logger::setMessage(get_called_class().': Should not remove this node ('.$node->nodeName.') ratio: '.$ratio.'%'); + + return false; + } + + return true; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/ParserInterface.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/ParserInterface.php new file mode 100644 index 0000000..3ded4b1 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/ParserInterface.php @@ -0,0 +1,20 @@ +getRulesFileList($hostname); + + foreach ($this->getRulesFolders() as $folder) { + $rule = $this->loadRuleFile($folder, $files); + + if (!empty($rule)) { + return $rule; + } + } + } + + return array(); + } + + /** + * Get the list of possible rules file names for a given hostname. + * + * @param string $hostname Hostname + * @return array + */ + public function getRulesFileList($hostname) + { + $files = array($hostname); // subdomain.domain.tld + $parts = explode('.', $hostname); + $len = count($parts); + + if ($len > 2) { + $subdomain = array_shift($parts); + $files[] = implode('.', $parts); // domain.tld + $files[] = '.'.implode('.', $parts); // .domain.tld + $files[] = $subdomain; // subdomain + } elseif ($len === 2) { + $files[] = '.'.implode('.', $parts); // .domain.tld + $files[] = $parts[0]; // domain + } + + return $files; + } + + /** + * Load a rule file from the defined folder. + * + * @param string $folder Rule directory + * @param array $files List of possible file names + * @return array + */ + public function loadRuleFile($folder, array $files) + { + foreach ($files as $file) { + $filename = $folder.'/'.$file.'.php'; + if (file_exists($filename)) { + Logger::setMessage(get_called_class().' Load rule: '.$file); + + return include $filename; + } + } + + return array(); + } + + /** + * Get the list of folders that contains rules. + * + * @return array + */ + public function getRulesFolders() + { + $folders = array(); + + if ($this->config !== null && $this->config->getGrabberRulesFolder() !== null) { + $folders[] = $this->config->getGrabberRulesFolder(); + } + + $folders[] = __DIR__ . '/../Rules'; + + return $folders; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/RuleParser.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/RuleParser.php new file mode 100644 index 0000000..9beb59c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/RuleParser.php @@ -0,0 +1,102 @@ +rules = $rules; + $this->dom = XmlParser::getHtmlDocument(''.$html); + $this->xpath = new DOMXPath($this->dom); + } + + /** + * Get the relevant content with predefined rules. + * + * @return string + */ + public function execute() + { + $this->stripTags(); + + return $this->findContent(); + } + + /** + * Remove HTML tags. + */ + public function stripTags() + { + if (isset($this->rules['strip']) && is_array($this->rules['strip'])) { + foreach ($this->rules['strip'] as $pattern) { + $nodes = $this->xpath->query($pattern); + + if ($nodes !== false && $nodes->length > 0) { + foreach ($nodes as $node) { + $node->parentNode->removeChild($node); + } + } + } + } + } + + /** + * Fetch content based on Xpath rules. + */ + public function findContent() + { + $content = ''; + if (isset($this->rules['body']) && is_array($this->rules['body'])) { + foreach ($this->rules['body'] as $pattern) { + $nodes = $this->xpath->query($pattern); + + if ($nodes !== false && $nodes->length > 0) { + foreach ($nodes as $node) { + $content .= $this->dom->saveXML($node); + } + } + } + } + + return $content; + } + + /** + * Fetch next link based on Xpath rules. + * + * @return string + */ + public function findNextLink() + { + if (isset($this->rules['next_page']) && is_array($this->rules['next_page'])) { + foreach ($this->rules['next_page'] as $pattern) { + $nodes = $this->xpath->query($pattern); + if ($nodes !== false && $nodes->length > 0) { + foreach ($nodes as $node) { + return $node->getAttribute('href'); + } + } + } + } + return null; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/Scraper.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/Scraper.php new file mode 100644 index 0000000..29383b2 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Scraper/Scraper.php @@ -0,0 +1,282 @@ +enableCandidateParser = false; + return $this; + } + + /** + * Get encoding. + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set encoding. + * + * @param string $encoding + * + * @return Scraper + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + + return $this; + } + + /** + * Get URL to download. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set URL to download. + * + * @param string $url URL + * + * @return Scraper + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * Return true if the scraper found relevant content. + * + * @return bool + */ + public function hasRelevantContent() + { + return !empty($this->content); + } + + /** + * Get relevant content. + * + * @return string + */ + public function getRelevantContent() + { + return $this->content; + } + + /** + * Get raw content (unfiltered). + * + * @return string + */ + public function getRawContent() + { + return $this->html; + } + + /** + * Set raw content (unfiltered). + * + * @param string $html + * + * @return Scraper + */ + public function setRawContent($html) + { + $this->html = $html; + + return $this; + } + + /** + * Get filtered relevant content. + * + * @return string + */ + public function getFilteredContent() + { + $filter = Filter::html($this->content, $this->url); + $filter->setConfig($this->config); + + return $filter->execute(); + } + + /** + * Download the HTML content. + * + * @return bool + */ + public function download() + { + if (!empty($this->url)) { + + // Clear everything + $this->html = ''; + $this->content = ''; + $this->encoding = ''; + + try { + $client = Client::getInstance(); + $client->setConfig($this->config); + $client->setTimeout($this->config->getGrabberTimeout()); + $client->setUserAgent($this->config->getGrabberUserAgent()); + $client->execute($this->url); + + $this->url = $client->getUrl(); + $this->html = $client->getContent(); + $this->encoding = $client->getEncoding(); + + return true; + } catch (ClientException $e) { + Logger::setMessage(get_called_class().': '.$e->getMessage()); + } + } + + return false; + } + + /** + * Execute the scraper. + * + * @param string $pageContent + * @param int $recursionDepth + */ + public function execute($pageContent = '', $recursionDepth = 0) + { + $this->html = ''; + $this->encoding = ''; + $this->content = ''; + $this->download(); + $this->prepareHtml(); + + $parser = $this->getParser(); + + if ($parser !== null) { + $maxRecursions = $this->config->getMaxRecursions(); + if(!isset($maxRecursions)){ + $maxRecursions = 25; + } + $pageContent .= $parser->execute(); + // check if there is a link to next page and recursively get content (max 25 pages) + if((($nextLink = $parser->findNextLink()) !== null) && $recursionDepth < $maxRecursions){ + $nextLink = Url::resolve($nextLink,$this->url); + $this->setUrl($nextLink); + $this->execute($pageContent,$recursionDepth+1); + } + else{ + $this->content = $pageContent; + } + Logger::setMessage(get_called_class().': Content length: '.strlen($this->content).' bytes'); + } + } + + /** + * Get the parser. + * + * @return ParserInterface + */ + public function getParser() + { + $ruleLoader = new RuleLoader($this->config); + $rules = $ruleLoader->getRules($this->url); + + if (!empty($rules['grabber'])) { + Logger::setMessage(get_called_class().': Parse content with rules'); + + foreach ($rules['grabber'] as $pattern => $rule) { + $url = new Url($this->url); + $sub_url = $url->getFullPath(); + + if (preg_match($pattern, $sub_url)) { + Logger::setMessage(get_called_class().': Matched url '.$sub_url); + return new RuleParser($this->html, $rule); + } + } + } elseif ($this->enableCandidateParser) { + Logger::setMessage(get_called_class().': Parse content with candidates'); + } + + return new CandidateParser($this->html); + } + + /** + * Normalize encoding and strip head tag. + */ + public function prepareHtml() + { + $html_encoding = XmlParser::getEncodingFromMetaTag($this->html); + + $this->html = Encoding::convert($this->html, $html_encoding ?: $this->encoding); + $this->html = Filter::stripHeadTags($this->html); + + Logger::setMessage(get_called_class().': HTTP Encoding "'.$this->encoding.'" ; HTML Encoding "'.$html_encoding.'"'); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/Subscription.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/Subscription.php new file mode 100644 index 0000000..12eccfd --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/Subscription.php @@ -0,0 +1,175 @@ +title = $title; + return $this; + } + + /** + * Get title + * + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set feed URL + * + * @access public + * @param string $feedUrl + * @return Subscription + */ + public function setFeedUrl($feedUrl) + { + $this->feedUrl = $feedUrl; + return $this; + } + + /** + * Get feed URL + * + * @access public + * @return string + */ + public function getFeedUrl() + { + return $this->feedUrl; + } + + /** + * Set site URL + * + * @access public + * @param string $siteUrl + * @return Subscription + */ + public function setSiteUrl($siteUrl) + { + $this->siteUrl = $siteUrl; + return $this; + } + + /** + * Get site URL + * + * @access public + * @return string + */ + public function getSiteUrl() + { + return $this->siteUrl; + } + + /** + * Set category + * + * @access public + * @param string $category + * @return Subscription + */ + public function setCategory($category) + { + $this->category = $category; + return $this; + } + + /** + * Get category + * + * @access public + * @return string + */ + public function getCategory() + { + return $this->category; + } + + /** + * Set description + * + * @access public + * @param string $description + * @return Subscription + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + + /** + * Get description + * + * @access public + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set type + * + * @access public + * @param string $type + * @return Subscription + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Get type + * + * @access public + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionList.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionList.php new file mode 100644 index 0000000..b173f89 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionList.php @@ -0,0 +1,75 @@ +title = $title; + return $this; + } + + /** + * Get title + * + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Add subscription + * + * @access public + * @param Subscription $subscription + * @return SubscriptionList + */ + public function addSubscription(Subscription $subscription) + { + $this->subscriptions[] = $subscription; + return $this; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListBuilder.php new file mode 100644 index 0000000..838e4cb --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListBuilder.php @@ -0,0 +1,204 @@ +subscriptionList = $subscriptionList; + } + + /** + * Get object instance + * + * @static + * @access public + * @param SubscriptionList $subscriptionList + * @return SubscriptionListBuilder + */ + public static function create(SubscriptionList $subscriptionList) + { + return new static($subscriptionList); + } + + /** + * Build OPML feed + * + * @access public + * @param string $filename + * @return string + */ + public function build($filename = '') + { + $this->document = new DomDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $opmlElement = $this->document->createElement('opml'); + $opmlElement->setAttribute('version', '1.0'); + + $headElement = $this->document->createElement('head'); + + if ($this->subscriptionList->getTitle() !== '') { + $titleElement = $this->document->createElement('title'); + $titleElement->appendChild($this->document->createTextNode($this->subscriptionList->getTitle())); + $headElement->appendChild($titleElement); + } + + $opmlElement->appendChild($headElement); + $opmlElement->appendChild($this->buildBody()); + $this->document->appendChild($opmlElement); + + if ($filename !== '') { + $this->document->save($filename); + return ''; + } + + return $this->document->saveXML(); + } + + /** + * Return true if the list has categories + * + * @access public + * @return bool + */ + public function hasCategories() + { + foreach ($this->subscriptionList->subscriptions as $subscription) { + if ($subscription->getCategory() !== '') { + return true; + } + } + + return false; + } + + /** + * Build OPML body + * + * @access protected + * @return DOMElement + */ + protected function buildBody() + { + $bodyElement = $this->document->createElement('body'); + + if ($this->hasCategories()) { + $this->buildCategories($bodyElement); + return $bodyElement; + } + + foreach ($this->subscriptionList->subscriptions as $subscription) { + $bodyElement->appendChild($this->buildSubscription($subscription)); + } + + return $bodyElement; + } + + /** + * Build categories section + * + * @access protected + * @param DOMElement $bodyElement + */ + protected function buildCategories(DOMElement $bodyElement) + { + $categories = $this->groupByCategories(); + + foreach ($categories as $category => $subscriptions) { + $bodyElement->appendChild($this->buildCategory($category, $subscriptions)); + } + } + + /** + * Build category tag + * + * @access protected + * @param string $category + * @param array $subscriptions + * @return DOMElement + */ + protected function buildCategory($category, array $subscriptions) + { + $outlineElement = $this->document->createElement('outline'); + $outlineElement->setAttribute('text', $category); + + foreach ($subscriptions as $subscription) { + $outlineElement->appendChild($this->buildSubscription($subscription)); + } + + return $outlineElement; + } + + /** + * Build subscription entry + * + * @access public + * @param Subscription $subscription + * @return DOMElement + */ + protected function buildSubscription(Subscription $subscription) + { + $outlineElement = $this->document->createElement('outline'); + $outlineElement->setAttribute('type', $subscription->getType() ?: 'rss'); + $outlineElement->setAttribute('text', $subscription->getTitle() ?: $subscription->getFeedUrl()); + $outlineElement->setAttribute('xmlUrl', $subscription->getFeedUrl()); + + if ($subscription->getTitle() !== '') { + $outlineElement->setAttribute('title', $subscription->getTitle()); + } + + if ($subscription->getDescription() !== '') { + $outlineElement->setAttribute('description', $subscription->getDescription()); + } + + if ($subscription->getSiteUrl() !== '') { + $outlineElement->setAttribute('htmlUrl', $subscription->getSiteUrl()); + } + + return $outlineElement; + } + + /** + * Group subscriptions by category + * + * @access private + * @return array + */ + private function groupByCategories() + { + $categories = array(); + + foreach ($this->subscriptionList->subscriptions as $subscription) { + $categories[$subscription->getCategory()][] = $subscription; + } + + return $categories; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListParser.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListParser.php new file mode 100644 index 0000000..9085588 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionListParser.php @@ -0,0 +1,100 @@ +subscriptionList = new SubscriptionList(); + $this->data = trim($data); + } + + /** + * Get object instance + * + * @static + * @access public + * @param string $data + * @return SubscriptionListParser + */ + public static function create($data) + { + return new static($data); + } + + /** + * Parse a subscription list entry + * + * @access public + * @throws MalformedXmlException + * @return SubscriptionList + */ + public function parse() + { + $xml = XmlParser::getSimpleXml($this->data); + + if (! $xml || !isset($xml->head) || !isset($xml->body)) { + throw new MalformedXmlException('Unable to parse OPML file: invalid XML'); + } + + $this->parseTitle($xml->head); + $this->parseEntries($xml->body); + + return $this->subscriptionList; + } + + /** + * Parse title + * + * @access protected + * @param SimpleXMLElement $xml + */ + protected function parseTitle(SimpleXMLElement $xml) + { + $this->subscriptionList->setTitle((string) $xml->title); + } + + /** + * Parse entries + * + * @access protected + * @param SimpleXMLElement $body + */ + private function parseEntries(SimpleXMLElement $body) + { + foreach ($body->outline as $outlineElement) { + if (isset($outlineElement->outline)) { + $this->parseEntries($outlineElement); + } else { + $this->subscriptionList->subscriptions[] = SubscriptionParser::create($body, $outlineElement)->parse(); + } + } + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionParser.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionParser.php new file mode 100644 index 0000000..caff07c --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Serialization/SubscriptionParser.php @@ -0,0 +1,142 @@ +parentElement = $parentElement; + $this->outlineElement = $outlineElement; + $this->subscription = new Subscription(); + } + + /** + * Get object instance + * + * @static + * @access public + * @param SimpleXMLElement $parentElement + * @param SimpleXMLElement $outlineElement + * @return SubscriptionParser + */ + public static function create(SimpleXMLElement $parentElement, SimpleXMLElement $outlineElement) + { + return new static($parentElement, $outlineElement); + } + + /** + * Parse subscription entry + * + * @access public + * @return Subscription + */ + public function parse() + { + $this->subscription->setCategory($this->findCategory()); + $this->subscription->setTitle($this->findTitle()); + $this->subscription->setFeedUrl($this->findFeedUrl()); + $this->subscription->setSiteUrl($this->findSiteUrl()); + $this->subscription->setType($this->findType()); + $this->subscription->setDescription($this->findDescription()); + + return $this->subscription; + } + + /** + * Find category. + * + * @access protected + * @return string + */ + protected function findCategory() + { + return isset($this->parentElement['text']) ? (string) $this->parentElement['text'] : ''; + } + + /** + * Find title. + * + * @access protected + * @return string + */ + protected function findTitle() + { + return isset($this->outlineElement['title']) ? (string) $this->outlineElement['title'] : (string) $this->outlineElement['text']; + } + + /** + * Find feed url. + * + * @access protected + * @return string + */ + protected function findFeedUrl() + { + return (string) $this->outlineElement['xmlUrl']; + } + + /** + * Find site url. + * + * @access protected + * @return string + */ + protected function findSiteUrl() + { + return isset($this->outlineElement['htmlUrl']) ? (string) $this->outlineElement['htmlUrl'] : $this->findFeedUrl(); + } + + /** + * Find type. + * + * @access protected + * @return string + */ + protected function findType() + { + return isset($this->outlineElement['version']) ? (string) $this->outlineElement['version'] : + isset($this->outlineElement['type']) ? (string) $this->outlineElement['type'] : 'rss'; + } + + /** + * Find description. + * + * @access protected + * @return string + */ + protected function findDescription() + { + return isset($this->outlineElement['description']) ? (string) $this->outlineElement['description'] : $this->findTitle(); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomFeedBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomFeedBuilder.php new file mode 100644 index 0000000..34f3780 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomFeedBuilder.php @@ -0,0 +1,65 @@ +helper = new AtomHelper($this->getDocument()); + + $this->feedElement = $this->getDocument()->createElement('feed'); + $this->feedElement->setAttributeNodeNS(new DomAttr('xmlns', 'http://www.w3.org/2005/Atom')); + + $generator = $this->getDocument()->createElement('generator', 'PicoFeed'); + $generator->setAttribute('uri', 'https://github.com/miniflux/picoFeed'); + $this->feedElement->appendChild($generator); + + $this->helper + ->buildTitle($this->feedElement, $this->feedTitle) + ->buildId($this->feedElement, $this->feedUrl) + ->buildDate($this->feedElement, $this->feedDate) + ->buildLink($this->feedElement, $this->siteUrl) + ->buildLink($this->feedElement, $this->feedUrl, 'self', 'application/atom+xml') + ->buildAuthor($this->feedElement, $this->authorName, $this->authorEmail, $this->authorUrl) + ; + + foreach ($this->items as $item) { + $this->feedElement->appendChild($item->build()); + } + + $this->getDocument()->appendChild($this->feedElement); + + if ($filename !== '') { + $this->getDocument()->save($filename); + } + + return $this->getDocument()->saveXML(); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomHelper.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomHelper.php new file mode 100644 index 0000000..def6b0b --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomHelper.php @@ -0,0 +1,139 @@ +document = $document; + } + + /** + * Build node + * + * @access public + * @param DOMElement $element + * @param string $tag + * @param string $value + * @return AtomHelper + */ + public function buildNode(DOMElement $element, $tag, $value) + { + $node = $this->document->createElement($tag); + $node->appendChild($this->document->createTextNode($value)); + $element->appendChild($node); + return $this; + } + + /** + * Build title + * + * @access public + * @param DOMElement $element + * @param string $title + * @return AtomHelper + */ + public function buildTitle(DOMElement $element, $title) + { + return $this->buildNode($element, 'title', $title); + } + + /** + * Build id + * + * @access public + * @param DOMElement $element + * @param string $id + * @return AtomHelper + */ + public function buildId(DOMElement $element, $id) + { + return $this->buildNode($element, 'id', $id); + } + + /** + * Build date element + * + * @access public + * @param DOMElement $element + * @param DateTime $date + * @param string $type + * @return AtomHelper + */ + public function buildDate(DOMElement $element, DateTime $date, $type = 'updated') + { + return $this->buildNode($element, $type, $date->format(DateTime::ATOM)); + } + + /** + * Build link element + * + * @access public + * @param DOMElement $element + * @param string $url + * @param string $rel + * @param string $type + * @return AtomHelper + */ + public function buildLink(DOMElement $element, $url, $rel = 'alternate', $type = 'text/html') + { + $node = $this->document->createElement('link'); + $node->setAttribute('rel', $rel); + $node->setAttribute('type', $type); + $node->setAttribute('href', $url); + $element->appendChild($node); + + return $this; + } + + /** + * Build author element + * + * @access public + * @param DOMElement $element + * @param string $authorName + * @param string $authorEmail + * @param string $authorUrl + * @return AtomHelper + */ + public function buildAuthor(DOMElement $element, $authorName, $authorEmail, $authorUrl) + { + if (!empty($authorName)) { + $author = $this->document->createElement('author'); + $this->buildNode($author, 'name', $authorName); + + if (!empty($authorEmail)) { + $this->buildNode($author, 'email', $authorEmail); + } + + if (!empty($authorUrl)) { + $this->buildNode($author, 'uri', $authorUrl); + } + + $element->appendChild($author); + } + + return $this; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomItemBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomItemBuilder.php new file mode 100644 index 0000000..dfdfe68 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/AtomItemBuilder.php @@ -0,0 +1,63 @@ +itemElement = $this->feedBuilder->getDocument()->createElement('entry'); + $this->helper = new AtomHelper($this->feedBuilder->getDocument()); + + if (!empty($this->itemId)) { + $this->helper->buildId($this->itemElement, $this->itemId); + } else { + $this->helper->buildId($this->itemElement, $this->itemUrl); + } + + $this->helper + ->buildTitle($this->itemElement, $this->itemTitle) + ->buildLink($this->itemElement, $this->itemUrl) + ->buildDate($this->itemElement, $this->itemUpdatedDate, 'updated') + ->buildDate($this->itemElement, $this->itemPublishedDate, 'published') + ->buildAuthor($this->itemElement, $this->authorName, $this->authorEmail, $this->authorUrl) + ; + + if (!empty($this->itemSummary)) { + $this->helper->buildNode($this->itemElement, 'summary', $this->itemSummary); + } + + if (!empty($this->itemContent)) { + $node = $this->feedBuilder->getDocument()->createElement('content'); + $node->setAttribute('type', 'html'); + $node->appendChild($this->feedBuilder->getDocument()->createCDATASection($this->itemContent)); + $this->itemElement->appendChild($node); + } + + return $this->itemElement; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/FeedBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/FeedBuilder.php new file mode 100644 index 0000000..cf9d024 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/FeedBuilder.php @@ -0,0 +1,185 @@ +document = new DomDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + } + + /** + * Get new object instance + * + * @access public + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Add feed title + * + * @access public + * @param string $title + * @return $this + */ + public function withTitle($title) + { + $this->feedTitle = $title; + return $this; + } + + /** + * Add feed url + * + * @access public + * @param string $url + * @return $this + */ + public function withFeedUrl($url) + { + $this->feedUrl = $url; + return $this; + } + + /** + * Add website url + * + * @access public + * @param string $url + * @return $this + */ + public function withSiteUrl($url) + { + $this->siteUrl = $url; + return $this; + } + + /** + * Add feed date + * + * @access public + * @param DateTime $date + * @return $this + */ + public function withDate(DateTime $date) + { + $this->feedDate = $date; + return $this; + } + + /** + * Add feed author + * + * @access public + * @param string $name + * @param string $email + * @param string $url + * @return $this + */ + public function withAuthor($name, $email = '', $url ='') + { + $this->authorName = $name; + $this->authorEmail = $email; + $this->authorUrl = $url; + return $this; + } + + /** + * Add feed item + * + * @access public + * @param ItemBuilder $item + * @return $this + */ + public function withItem(ItemBuilder $item) + { + $this->items[] = $item; + return $this; + } + + /** + * Get DOM document + * + * @access public + * @return DOMDocument + */ + public function getDocument() + { + return $this->document; + } + + /** + * Build feed + * + * @abstract + * @access public + * @param string $filename + * @return string + */ + abstract public function build($filename = ''); +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/ItemBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/ItemBuilder.php new file mode 100644 index 0000000..86985bc --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/ItemBuilder.php @@ -0,0 +1,209 @@ +feedBuilder = $feedBuilder; + } + + /** + * Get new object instance + * + * @access public + * @param FeedBuilder $feedBuilder + * @return static + */ + public static function create(FeedBuilder $feedBuilder) + { + return new static($feedBuilder); + } + + /** + * Add item title + * + * @access public + * @param string $title + * @return $this + */ + public function withTitle($title) + { + $this->itemTitle = $title; + return $this; + } + + /** + * Add item id + * + * @access public + * @param string $id + * @return $this + */ + public function withId($id) + { + $this->itemId = $id; + return $this; + } + + /** + * Add item url + * + * @access public + * @param string $url + * @return $this + */ + public function withUrl($url) + { + $this->itemUrl = $url; + return $this; + } + + /** + * Add item summary + * + * @access public + * @param string $summary + * @return $this + */ + public function withSummary($summary) + { + $this->itemSummary = $summary; + return $this; + } + + /** + * Add item content + * + * @access public + * @param string $content + * @return $this + */ + public function withContent($content) + { + $this->itemContent = $content; + return $this; + } + + /** + * Add item updated date + * + * @access public + * @param DateTime $date + * @return $this + */ + public function withUpdatedDate(DateTime $date) + { + $this->itemUpdatedDate = $date; + return $this; + } + + /** + * Add item published date + * + * @access public + * @param DateTime $date + * @return $this + */ + public function withPublishedDate(DateTime $date) + { + $this->itemPublishedDate = $date; + return $this; + } + + /** + * Add item author + * + * @access public + * @param string $name + * @param string $email + * @param string $url + * @return $this + */ + public function withAuthor($name, $email = '', $url ='') + { + $this->authorName = $name; + $this->authorEmail = $email; + $this->authorUrl = $url; + return $this; + } + + /** + * Build item + * + * @abstract + * @access public + * @return DOMElement + */ + abstract public function build(); +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20FeedBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20FeedBuilder.php new file mode 100644 index 0000000..bc3f513 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20FeedBuilder.php @@ -0,0 +1,76 @@ +helper = new Rss20Helper($this->getDocument()); + + $this->rssElement = $this->getDocument()->createElement('rss'); + $this->rssElement->setAttribute('version', '2.0'); + $this->rssElement->setAttributeNodeNS(new DomAttr('xmlns:content', 'http://purl.org/rss/1.0/modules/content/')); + $this->rssElement->setAttributeNodeNS(new DomAttr('xmlns:atom', 'http://www.w3.org/2005/Atom')); + + $this->channelElement = $this->getDocument()->createElement('channel'); + $this->helper + ->buildNode($this->channelElement, 'generator', 'PicoFeed (https://github.com/miniflux/picoFeed)') + ->buildTitle($this->channelElement, $this->feedTitle) + ->buildNode($this->channelElement, 'description', $this->feedTitle) + ->buildDate($this->channelElement, $this->feedDate) + ->buildAuthor($this->channelElement, 'webMaster', $this->authorName, $this->authorEmail) + ->buildLink($this->channelElement, $this->siteUrl) + ; + + $link = $this->getDocument()->createElement('atom:link'); + $link->setAttribute('href', $this->feedUrl); + $link->setAttribute('rel', 'self'); + $link->setAttribute('type', 'application/rss+xml'); + $this->channelElement->appendChild($link); + + foreach ($this->items as $item) { + $this->channelElement->appendChild($item->build()); + } + + $this->rssElement->appendChild($this->channelElement); + $this->getDocument()->appendChild($this->rssElement); + + if ($filename !== '') { + $this->getDocument()->save($filename); + } + + return $this->getDocument()->saveXML(); + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20Helper.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20Helper.php new file mode 100644 index 0000000..72a19e5 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20Helper.php @@ -0,0 +1,115 @@ +document = $document; + } + + /** + * Build node + * + * @access public + * @param DOMElement $element + * @param string $tag + * @param string $value + * @return $this + */ + public function buildNode(DOMElement $element, $tag, $value) + { + $node = $this->document->createElement($tag); + $node->appendChild($this->document->createTextNode($value)); + $element->appendChild($node); + return $this; + } + + /** + * Build title + * + * @access public + * @param DOMElement $element + * @param string $title + * @return $this + */ + public function buildTitle(DOMElement $element, $title) + { + return $this->buildNode($element, 'title', $title); + } + + /** + * Build date element + * + * @access public + * @param DOMElement $element + * @param DateTime $date + * @param string $type + * @return $this + */ + public function buildDate(DOMElement $element, DateTime $date, $type = 'pubDate') + { + return $this->buildNode($element, $type, $date->format(DateTime::RSS)); + } + + /** + * Build link element + * + * @access public + * @param DOMElement $element + * @param string $url + * @return $this + */ + public function buildLink(DOMElement $element, $url) + { + return $this->buildNode($element, 'link', $url); + } + + /** + * Build author element + * + * @access public + * @param DOMElement $element + * @param string $tag + * @param string $authorName + * @param string $authorEmail + * @return $this + */ + public function buildAuthor(DOMElement $element, $tag, $authorName, $authorEmail) + { + if (!empty($authorName)) { + $value = ''; + + if (!empty($authorEmail)) { + $value .= $authorEmail.' ('.$authorName.')'; + } else { + $value = $authorName; + } + + $this->buildNode($element, $tag, $value); + } + + return $this; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20ItemBuilder.php b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20ItemBuilder.php new file mode 100644 index 0000000..125dc6a --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/lib/PicoFeed/Syndication/Rss20ItemBuilder.php @@ -0,0 +1,67 @@ +itemElement = $this->feedBuilder->getDocument()->createElement('item'); + $this->helper = new Rss20Helper($this->feedBuilder->getDocument()); + + if (!empty($this->itemId)) { + $guid = $this->feedBuilder->getDocument()->createElement('guid'); + $guid->setAttribute('isPermaLink', 'false'); + $guid->appendChild($this->feedBuilder->getDocument()->createTextNode($this->itemId)); + $this->itemElement->appendChild($guid); + } else { + $guid = $this->feedBuilder->getDocument()->createElement('guid'); + $guid->setAttribute('isPermaLink', 'true'); + $guid->appendChild($this->feedBuilder->getDocument()->createTextNode($this->itemUrl)); + $this->itemElement->appendChild($guid); + } + + $this->helper + ->buildTitle($this->itemElement, $this->itemTitle) + ->buildLink($this->itemElement, $this->itemUrl) + ->buildDate($this->itemElement, $this->itemPublishedDate) + ->buildAuthor($this->itemElement, 'author', $this->authorName, $this->authorEmail) + ; + + if (!empty($this->itemSummary)) { + $this->helper->buildNode($this->itemElement, 'description', $this->itemSummary); + } + + if (!empty($this->itemContent)) { + $node = $this->feedBuilder->getDocument()->createElement('content:encoded'); + $node->appendChild($this->feedBuilder->getDocument()->createCDATASection($this->itemContent)); + $this->itemElement->appendChild($node); + } + + return $this->itemElement; + } +} diff --git a/plugins/admin/vendor/p3k/picofeed/picofeed b/plugins/admin/vendor/p3k/picofeed/picofeed new file mode 100644 index 0000000..8f35737 --- /dev/null +++ b/plugins/admin/vendor/p3k/picofeed/picofeed @@ -0,0 +1,135 @@ +#!/usr/bin/env php +discover($url); + + $parser = $reader->getParser( + $resource->getUrl(), + $resource->getContent(), + $resource->getEncoding() + ); + + if ($disable_filtering) { + $parser->disableContentFiltering(); + } + + return $parser->execute(); + } + catch (PicoFeedException $e) { + echo 'Exception thrown ===> "'.$e->getMessage().'"'.PHP_EOL; + return false; + } +} + +function get_item($feed, $item_id) +{ + foreach ($feed->items as $item) { + if ($item->getId() === $item_id) { + echo $item; + echo "============= CONTENT ================\n"; + echo $item->getContent(); + echo "\n============= CONTENT ================\n"; + break; + } + } +} + +function dump_feed($url) +{ + $feed = get_feed($url); + echo $feed; +} + +function debug_feed($url) +{ + get_feed($url); + print_r(Logger::getMessages()); +} + +function dump_item($url, $item_id) +{ + $feed = get_feed($url); + + if ($feed !== false) { + get_item($feed, $item_id); + } +} + +function nofilter_item($url, $item_id) +{ + $feed = get_feed($url, true); + + if ($feed !== false) { + get_item($feed, $item_id); + } +} + +function grabber($url) +{ + $grabber = new Scraper(new Config); + $grabber->setUrl($url); + $grabber->execute(); + + print_r(Logger::getMessages()); + echo "============= CONTENT ================\n"; + echo $grabber->getRelevantContent().PHP_EOL; + echo "============= FILTERED ================\n"; + echo $grabber->getFilteredContent().PHP_EOL; +} + +function fetch_favicon($url) +{ + $favicon = new Favicon(); + echo $favicon->find($url) . PHP_EOL; +} + +// Parse command line arguments +if ($argc === 4) { + switch ($argv[1]) { + case 'item': + dump_item($argv[2], $argv[3]); + die; + case 'nofilter': + nofilter_item($argv[2], $argv[3]); + die; + } +} else if ($argc === 3) { + switch ($argv[1]) { + case 'feed': + dump_feed($argv[2]); + die; + case 'debug': + debug_feed($argv[2]); + die; + case 'grabber': + grabber($argv[2]); + die; + case 'favicon': + fetch_favicon($argv[2]); + die; + } +} + +printf("Usage:\n"); +printf("%s feed \n", $argv[0]); +printf("%s debug \n", $argv[0]); +printf("%s item \n", $argv[0]); +printf("%s nofilter \n", $argv[0]); +printf("%s grabber \n", $argv[0]); +printf("%s favicon \n", $argv[0]); diff --git a/plugins/admin/vendor/scssphp/scssphp/LICENSE.md b/plugins/admin/vendor/scssphp/scssphp/LICENSE.md new file mode 100644 index 0000000..afcfdfb --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2015 Leaf Corcoran, http://scssphp.github.io/scssphp + +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/plugins/admin/vendor/scssphp/scssphp/README.md b/plugins/admin/vendor/scssphp/scssphp/README.md new file mode 100644 index 0000000..65bb93e --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/README.md @@ -0,0 +1,71 @@ +# scssphp +### + +![Build](https://github.com/scssphp/scssphp/workflows/CI/badge.svg) +[![License](https://poser.pugx.org/scssphp/scssphp/license)](https://packagist.org/packages/scssphp/scssphp) + +`scssphp` is a compiler for SCSS written in PHP. + +Checkout the homepage, , for directions on how to use. + +## Running Tests + +`scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing. + +Run the following command from the root directory to run every test: + + vendor/bin/phpunit tests + +There are several tests in the `tests/` directory: + +* `ApiTest.php` contains various unit tests that test the PHP interface. +* `ExceptionTest.php` contains unit tests that test for exceptions thrown by the parser and compiler. +* `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs. +* `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory + then compares to the respective `.css` file in the `tests/outputs` directory. +* `SassSpecTest.php` extracts tests from the `sass/sass-spec` repository. + +When changing any of the tests in `tests/inputs`, the tests will most likely +fail because the output has changed. Once you verify that the output is correct +you can run the following command to rebuild all the tests: + + BUILD=1 vendor/bin/phpunit tests + +This will compile all the tests, and save results into `tests/outputs`. It also +updates the list of excluded specs from sass-spec. + +To enable the full `sass-spec` compatibility tests: + + TEST_SASS_SPEC=1 vendor/bin/phpunit tests + +## Coding Standard + +`scssphp` source conforms to [PSR12](https://www.php-fig.org/psr/psr-12/). + +Run the following command from the root directory to check the code for "sniffs". + + vendor/bin/phpcs --standard=PSR12 --extensions=php bin src tests *.php + +## Static Analysis + +`scssphp` uses [phpstan](https://phpstan.org/) for static analysis. + +Run the following command from the root directory to analyse the codebase: + + make phpstan + +As most of the codebase is composed of legacy code which cannot be type-checked +fully, the setup contains a baseline file with all errors we want to ignore. In +particular, we ignore all errors related to not specifying the types inside arrays +when these arrays correspond to the representation of Sass values and Sass AST nodes +in the parser and compiler. +When contributing, the proper process to deal with static analysis is the following: + +1. Make your change in the codebase +2. Run `make phpstan` +3. Fix errors reported by phpstan when possible +4. Repeat step 2 and 3 until nothing gets fixed anymore at step 3 +5. Run `make phpstan-baseline` to regenerate the phpstan baseline + +Additions to the baseline will be reviewed to avoid ignoring errors that should have +been fixed. diff --git a/plugins/admin/vendor/scssphp/scssphp/bin/pscss b/plugins/admin/vendor/scssphp/scssphp/bin/pscss new file mode 100644 index 0000000..e622398 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/bin/pscss @@ -0,0 +1,244 @@ +#!/usr/bin/env php +parse($data)), true)); + + fwrite(STDERR, 'Warning: the --dump-tree option is deprecated. Use proper debugging tools instead.'); + + exit(); +} + +$scss = new Compiler(); + +if ($loadPaths) { + $scss->setImportPaths($loadPaths); +} + +if ($style) { + if ($style === OutputStyle::COMPRESSED || $style === OutputStyle::EXPANDED) { + $scss->setOutputStyle($style); + } else { + fwrite(STDERR, "WARNING: the $style style is deprecated.\n"); + $scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style)); + } +} + +$outputFile = isset($arguments[1]) ? $arguments[1] : null; +$sourceMapFile = null; + +if ($sourceMap) { + $sourceMapOptions = array( + 'outputSourceFiles' => $embedSources, + ); + if ($embedSourceMap || $outputFile === null) { + $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE); + } else { + $sourceMapFile = $outputFile . '.map'; + $sourceMapOptions['sourceMapWriteTo'] = $sourceMapFile; + $sourceMapOptions['sourceMapURL'] = basename($sourceMapFile); + $sourceMapOptions['sourceMapBasepath'] = getcwd(); + $sourceMapOptions['sourceMapFilename'] = basename($outputFile); + + $scss->setSourceMap(Compiler::SOURCE_MAP_FILE); + } + + $scss->setSourceMapOptions($sourceMapOptions); +} + +if ($encoding) { + $scss->setEncoding($encoding); +} + +try { + $result = $scss->compileString($data, $inputFile); +} catch (SassException $e) { + fwrite(STDERR, 'Error: '.$e->getMessage()."\n"); + exit(1); +} + +if ($outputFile) { + file_put_contents($outputFile, $result->getCss()); + + if ($sourceMapFile !== null && $result->getSourceMap() !== null) { + file_put_contents($sourceMapFile, $result->getSourceMap()); + } +} else { + echo $result->getCss(); +} diff --git a/plugins/admin/vendor/scssphp/scssphp/composer.json b/plugins/admin/vendor/scssphp/scssphp/composer.json new file mode 100644 index 0000000..86cd396 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/composer.json @@ -0,0 +1,108 @@ +{ + "name": "scssphp/scssphp", + "type": "library", + "description": "scssphp is a compiler for SCSS written in PHP.", + "keywords": ["css", "stylesheet", "scss", "sass", "less"], + "homepage": "http://scssphp.github.io/scssphp/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthon Pang", + "email": "apang@softwaredevelopment.ca", + "homepage": "https://github.com/robocoder" + }, + { + "name": "Cédric Morin", + "email": "cedric@yterium.com", + "homepage": "https://github.com/Cerdic" + } + ], + "autoload": { + "psr-4": { "ScssPhp\\ScssPhp\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "ScssPhp\\ScssPhp\\Tests\\": "tests/" } + }, + "require": { + "php": ">=5.6.0", + "ext-json": "*", + "ext-ctype": "*" + }, + "suggest": { + "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv", + "ext-iconv": "Can be used as fallback when ext-mbstring is not available" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4", + "sass/sass-spec": "*", + "squizlabs/php_codesniffer": "~3.5", + "symfony/phpunit-bridge": "^5.1", + "thoughtbot/bourbon": "^7.0", + "twbs/bootstrap": "~5.0", + "twbs/bootstrap4": "4.6.0", + "zurb/foundation": "~6.5" + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "sass/sass-spec", + "version": "2021.09.15", + "source": { + "type": "git", + "url": "https://github.com/sass/sass-spec.git", + "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sass/sass-spec/zipball/eb2d7a0865c1faf0b55a39ff962b24aca9b4c955", + "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955", + "shasum": "" + } + } + }, + { + "type": "package", + "package": { + "name": "thoughtbot/bourbon", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/thoughtbot/bourbon.git", + "reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thoughtbot/bourbon/zipball/fbe338ee6807e7f7aa996d82c8a16f248bb149b3", + "reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3", + "shasum": "" + } + } + }, + { + "type": "package", + "package": { + "name": "twbs/bootstrap4", + "version": "v4.6.0", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/6ffb0b48e455430f8a5359ed689ad64c1143fac2", + "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2", + "shasum": "" + } + } + } + ], + "bin": ["bin/pscss"], + "config": { + "sort-packages": true + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/phpcs.xml.dist b/plugins/admin/vendor/scssphp/scssphp/phpcs.xml.dist new file mode 100644 index 0000000..b162dbd --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/phpcs.xml.dist @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/plugins/admin/vendor/scssphp/scssphp/scss.inc.php b/plugins/admin/vendor/scssphp/scssphp/scss.inc.php new file mode 100644 index 0000000..4598378 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/scss.inc.php @@ -0,0 +1,21 @@ + + * + * @internal + */ +class Range +{ + /** + * @var float|int + */ + public $first; + + /** + * @var float|int + */ + public $last; + + /** + * Initialize range + * + * @param integer|float $first + * @param integer|float $last + */ + public function __construct($first, $last) + { + $this->first = $first; + $this->last = $last; + } + + /** + * Test for inclusion in range + * + * @param integer|float $value + * + * @return boolean + */ + public function includes($value) + { + return $value >= $this->first && $value <= $this->last; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Block.php b/plugins/admin/vendor/scssphp/scssphp/src/Block.php new file mode 100644 index 0000000..3ae49d0 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Block.php @@ -0,0 +1,73 @@ + + * + * @internal + */ +class Block +{ + /** + * @var string + */ + public $type; + + /** + * @var \ScssPhp\ScssPhp\Block + */ + public $parent; + + /** + * @var string + */ + public $sourceName; + + /** + * @var integer + */ + public $sourceIndex; + + /** + * @var integer + */ + public $sourceLine; + + /** + * @var integer + */ + public $sourceColumn; + + /** + * @var array|null + */ + public $selectors; + + /** + * @var array + */ + public $comments; + + /** + * @var array + */ + public $children; + + /** + * @var \ScssPhp\ScssPhp\Block|null + */ + public $selfParent; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Cache.php b/plugins/admin/vendor/scssphp/scssphp/src/Cache.php new file mode 100644 index 0000000..9731c60 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Cache.php @@ -0,0 +1,272 @@ + + * + * @internal + */ +class Cache +{ + const CACHE_VERSION = 1; + + /** + * directory used for storing data + * + * @var string|false + */ + public static $cacheDir = false; + + /** + * prefix for the storing data + * + * @var string + */ + public static $prefix = 'scssphp_'; + + /** + * force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit + * + * @var bool|string + */ + public static $forceRefresh = false; + + /** + * specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up + * + * @var int + */ + public static $gcLifetime = 604800; + + /** + * array of already refreshed cache if $forceRefresh==='once' + * + * @var array + */ + protected static $refreshed = []; + + /** + * Constructor + * + * @param array $options + * + * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string} $options + */ + public function __construct($options) + { + // check $cacheDir + if (isset($options['cacheDir'])) { + self::$cacheDir = $options['cacheDir']; + } + + if (empty(self::$cacheDir)) { + throw new Exception('cacheDir not set'); + } + + if (isset($options['prefix'])) { + self::$prefix = $options['prefix']; + } + + if (empty(self::$prefix)) { + throw new Exception('prefix not set'); + } + + if (isset($options['forceRefresh'])) { + self::$forceRefresh = $options['forceRefresh']; + } + + self::checkCacheDir(); + } + + /** + * Get the cached result of $operation on $what, + * which is known as dependant from the content of $options + * + * @param string $operation parse, compile... + * @param mixed $what content key (e.g., filename to be treated) + * @param array $options any option that affect the operation result on the content + * @param int|null $lastModified last modified timestamp + * + * @return mixed + * + * @throws \Exception + */ + public function getCache($operation, $what, $options = [], $lastModified = null) + { + $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); + + if ( + ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' && + isset(self::$refreshed[$fileCache]))) && file_exists($fileCache) + ) { + $cacheTime = filemtime($fileCache); + + if ( + (\is_null($lastModified) || $cacheTime > $lastModified) && + $cacheTime + self::$gcLifetime > time() + ) { + $c = file_get_contents($fileCache); + $c = unserialize($c); + + if (\is_array($c) && isset($c['value'])) { + return $c['value']; + } + } + } + + return null; + } + + /** + * Put in cache the result of $operation on $what, + * which is known as dependant from the content of $options + * + * @param string $operation + * @param mixed $what + * @param mixed $value + * @param array $options + * + * @return void + */ + public function setCache($operation, $what, $value, $options = []) + { + $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); + + $c = ['value' => $value]; + $c = serialize($c); + + file_put_contents($fileCache, $c); + + if (self::$forceRefresh === 'once') { + self::$refreshed[$fileCache] = true; + } + } + + /** + * Get the cache name for the caching of $operation on $what, + * which is known as dependant from the content of $options + * + * @param string $operation + * @param mixed $what + * @param array $options + * + * @return string + */ + private static function cacheName($operation, $what, $options = []) + { + $t = [ + 'version' => self::CACHE_VERSION, + 'scssphpVersion' => Version::VERSION, + 'operation' => $operation, + 'what' => $what, + 'options' => $options + ]; + + $t = self::$prefix + . sha1(json_encode($t)) + . ".$operation" + . ".scsscache"; + + return $t; + } + + /** + * Check that the cache dir exists and is writeable + * + * @return void + * + * @throws \Exception + */ + public static function checkCacheDir() + { + self::$cacheDir = str_replace('\\', '/', self::$cacheDir); + self::$cacheDir = rtrim(self::$cacheDir, '/') . '/'; + + if (! is_dir(self::$cacheDir)) { + throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir); + } + + if (! is_writable(self::$cacheDir)) { + throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir); + } + } + + /** + * Delete unused cached files + * + * @return void + */ + public static function cleanCache() + { + static $clean = false; + + if ($clean || empty(self::$cacheDir)) { + return; + } + + $clean = true; + + // only remove files with extensions created by SCSSPHP Cache + // css files removed based on the list files + $removeTypes = ['scsscache' => 1]; + + $files = scandir(self::$cacheDir); + + if (! $files) { + return; + } + + $checkTime = time() - self::$gcLifetime; + + foreach ($files as $file) { + // don't delete if the file wasn't created with SCSSPHP Cache + if (strpos($file, self::$prefix) !== 0) { + continue; + } + + $parts = explode('.', $file); + $type = array_pop($parts); + + if (! isset($removeTypes[$type])) { + continue; + } + + $fullPath = self::$cacheDir . $file; + $mtime = filemtime($fullPath); + + // don't delete if it's a relatively new file + if ($mtime > $checkTime) { + continue; + } + + unlink($fullPath); + } + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Colors.php b/plugins/admin/vendor/scssphp/scssphp/src/Colors.php new file mode 100644 index 0000000..e836e3f --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Colors.php @@ -0,0 +1,247 @@ + + * + * @internal + */ +class Colors +{ + /** + * CSS Colors + * + * @see http://www.w3.org/TR/css3-color + * + * @var array + */ + protected static $cssColors = [ + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'cyan' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgrey' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'magenta' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'grey' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgrey' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50', + 'rebeccapurple' => '102,51,153', + 'transparent' => '0,0,0,0', + ]; + + /** + * Convert named color in a [r,g,b[,a]] array + * + * @param string $colorName + * + * @return int[]|null + */ + public static function colorNameToRGBa($colorName) + { + if (\is_string($colorName) && isset(static::$cssColors[$colorName])) { + $rgba = explode(',', static::$cssColors[$colorName]); + + // only case with opacity is transparent, with opacity=0, so we can intval on opacity also + $rgba = array_map('intval', $rgba); + + return $rgba; + } + + return null; + } + + /** + * Reverse conversion : from RGBA to a color name if possible + * + * @param integer $r + * @param integer $g + * @param integer $b + * @param integer|float $a + * + * @return string|null + */ + public static function RGBaToColorName($r, $g, $b, $a = 1) + { + static $reverseColorTable = null; + + if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b) || ! is_numeric($a)) { + return null; + } + + if ($a < 1) { + return null; + } + + if (\is_null($reverseColorTable)) { + $reverseColorTable = []; + + foreach (static::$cssColors as $name => $rgb_str) { + $rgb_str = explode(',', $rgb_str); + + if ( + \count($rgb_str) == 3 && + ! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])]) + ) { + $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name; + } + } + } + + if (isset($reverseColorTable[\intval($r)][\intval($g)][\intval($b)])) { + return $reverseColorTable[\intval($r)][\intval($g)][\intval($b)]; + } + + return null; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/CompilationResult.php b/plugins/admin/vendor/scssphp/scssphp/src/CompilationResult.php new file mode 100644 index 0000000..36adb0d --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/CompilationResult.php @@ -0,0 +1,69 @@ +css = $css; + $this->sourceMap = $sourceMap; + $this->includedFiles = $includedFiles; + } + + /** + * @return string + */ + public function getCss() + { + return $this->css; + } + + /** + * @return string[] + */ + public function getIncludedFiles() + { + return $this->includedFiles; + } + + /** + * The sourceMap content, if it was generated + * + * @return null|string + */ + public function getSourceMap() + { + return $this->sourceMap; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Compiler.php b/plugins/admin/vendor/scssphp/scssphp/src/Compiler.php new file mode 100644 index 0000000..58ba795 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Compiler.php @@ -0,0 +1,10087 @@ + + * + * @final Extending the Compiler is deprecated + */ +class Compiler +{ + /** + * @deprecated + */ + const LINE_COMMENTS = 1; + /** + * @deprecated + */ + const DEBUG_INFO = 2; + + /** + * @deprecated + */ + const WITH_RULE = 1; + /** + * @deprecated + */ + const WITH_MEDIA = 2; + /** + * @deprecated + */ + const WITH_SUPPORTS = 4; + /** + * @deprecated + */ + const WITH_ALL = 7; + + const SOURCE_MAP_NONE = 0; + const SOURCE_MAP_INLINE = 1; + const SOURCE_MAP_FILE = 2; + + /** + * @var array + */ + protected static $operatorNames = [ + '+' => 'add', + '-' => 'sub', + '*' => 'mul', + '/' => 'div', + '%' => 'mod', + + '==' => 'eq', + '!=' => 'neq', + '<' => 'lt', + '>' => 'gt', + + '<=' => 'lte', + '>=' => 'gte', + ]; + + /** + * @var array + */ + protected static $namespaces = [ + 'special' => '%', + 'mixin' => '@', + 'function' => '^', + ]; + + public static $true = [Type::T_KEYWORD, 'true']; + public static $false = [Type::T_KEYWORD, 'false']; + /** @deprecated */ + public static $NaN = [Type::T_KEYWORD, 'NaN']; + /** @deprecated */ + public static $Infinity = [Type::T_KEYWORD, 'Infinity']; + public static $null = [Type::T_NULL]; + public static $nullString = [Type::T_STRING, '', []]; + public static $defaultValue = [Type::T_KEYWORD, '']; + public static $selfSelector = [Type::T_SELF]; + public static $emptyList = [Type::T_LIST, '', []]; + public static $emptyMap = [Type::T_MAP, [], []]; + public static $emptyString = [Type::T_STRING, '"', []]; + public static $with = [Type::T_KEYWORD, 'with']; + public static $without = [Type::T_KEYWORD, 'without']; + + /** + * @var array + */ + protected $importPaths = []; + /** + * @var array + */ + protected $importCache = []; + + /** + * @var string[] + */ + protected $importedFiles = []; + + /** + * @var array + * @phpstan-var array + */ + protected $userFunctions = []; + /** + * @var array + */ + protected $registeredVars = []; + /** + * @var array + */ + protected $registeredFeatures = [ + 'extend-selector-pseudoclass' => false, + 'at-error' => true, + 'units-level-3' => true, + 'global-variable-shadowing' => false, + ]; + + /** + * @var string|null + */ + protected $encoding = null; + /** + * @var null + * @deprecated + */ + protected $lineNumberStyle = null; + + /** + * @var int|SourceMapGenerator + * @phpstan-var self::SOURCE_MAP_*|SourceMapGenerator + */ + protected $sourceMap = self::SOURCE_MAP_NONE; + + /** + * @var array + * @phpstan-var array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} + */ + protected $sourceMapOptions = []; + + /** + * @var bool + */ + private $charset = true; + + /** + * @var string|\ScssPhp\ScssPhp\Formatter + */ + protected $formatter = Expanded::class; + + /** + * @var Environment + */ + protected $rootEnv; + /** + * @var OutputBlock|null + */ + protected $rootBlock; + + /** + * @var \ScssPhp\ScssPhp\Compiler\Environment + */ + protected $env; + /** + * @var OutputBlock|null + */ + protected $scope; + /** + * @var Environment|null + */ + protected $storeEnv; + /** + * @var bool|null + * + * @deprecated + */ + protected $charsetSeen; + /** + * @var array + */ + protected $sourceNames; + + /** + * @var Cache|null + */ + protected $cache; + + /** + * @var bool + */ + protected $cacheCheckImportResolutions = false; + + /** + * @var int + */ + protected $indentLevel; + /** + * @var array[] + */ + protected $extends; + /** + * @var array + */ + protected $extendsMap; + + /** + * @var array + */ + protected $parsedFiles = []; + + /** + * @var Parser|null + */ + protected $parser; + /** + * @var int|null + */ + protected $sourceIndex; + /** + * @var int|null + */ + protected $sourceLine; + /** + * @var int|null + */ + protected $sourceColumn; + /** + * @var bool|null + */ + protected $shouldEvaluate; + /** + * @var null + * @deprecated + */ + protected $ignoreErrors; + /** + * @var bool + */ + protected $ignoreCallStackMessage = false; + + /** + * @var array[] + */ + protected $callStack = []; + + /** + * @var array + * @phpstan-var list + */ + private $resolvedImports = []; + + /** + * The directory of the currently processed file + * + * @var string|null + */ + private $currentDirectory; + + /** + * The directory of the input file + * + * @var string + */ + private $rootDirectory; + + /** + * @var bool + */ + private $legacyCwdImportPath = true; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var array + */ + private $warnedChildFunctions = []; + + /** + * Constructor + * + * @param array|null $cacheOptions + * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string, checkImportResolutions?: bool}|null $cacheOptions + */ + public function __construct($cacheOptions = null) + { + $this->sourceNames = []; + + if ($cacheOptions) { + $this->cache = new Cache($cacheOptions); + if (!empty($cacheOptions['checkImportResolutions'])) { + $this->cacheCheckImportResolutions = true; + } + } + + $this->logger = new StreamLogger(fopen('php://stderr', 'w'), true); + } + + /** + * Get compiler options + * + * @return array + * + * @internal + */ + public function getCompileOptions() + { + $options = [ + 'importPaths' => $this->importPaths, + 'registeredVars' => $this->registeredVars, + 'registeredFeatures' => $this->registeredFeatures, + 'encoding' => $this->encoding, + 'sourceMap' => serialize($this->sourceMap), + 'sourceMapOptions' => $this->sourceMapOptions, + 'formatter' => $this->formatter, + 'legacyImportPath' => $this->legacyCwdImportPath, + ]; + + return $options; + } + + /** + * Sets an alternative logger. + * + * Changing the logger in the middle of the compilation is not + * supported and will result in an undefined behavior. + * + * @param LoggerInterface $logger + * + * @return void + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Set an alternative error output stream, for testing purpose only + * + * @param resource $handle + * + * @return void + * + * @deprecated Use {@see setLogger} instead + */ + public function setErrorOuput($handle) + { + @trigger_error('The method "setErrorOuput" is deprecated. Use "setLogger" instead.', E_USER_DEPRECATED); + + $this->logger = new StreamLogger($handle); + } + + /** + * Compile scss + * + * @param string $code + * @param string|null $path + * + * @return string + * + * @throws SassException when the source fails to compile + * + * @deprecated Use {@see compileString} instead. + */ + public function compile($code, $path = null) + { + @trigger_error(sprintf('The "%s" method is deprecated. Use "compileString" instead.', __METHOD__), E_USER_DEPRECATED); + + $result = $this->compileString($code, $path); + + $sourceMap = $result->getSourceMap(); + + if ($sourceMap !== null) { + if ($this->sourceMap instanceof SourceMapGenerator) { + $this->sourceMap->saveMap($sourceMap); + } elseif ($this->sourceMap === self::SOURCE_MAP_FILE) { + $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + $sourceMapGenerator->saveMap($sourceMap); + } + } + + return $result->getCss(); + } + + /** + * Compile scss + * + * @param string $source + * @param string|null $path + * + * @return CompilationResult + * + * @throws SassException when the source fails to compile + */ + public function compileString($source, $path = null) + { + if ($this->cache) { + $cacheKey = ($path ? $path : '(stdin)') . ':' . md5($source); + $compileOptions = $this->getCompileOptions(); + $cachedResult = $this->cache->getCache('compile', $cacheKey, $compileOptions); + + if ($cachedResult instanceof CachedResult && $this->isFreshCachedResult($cachedResult)) { + return $cachedResult->getResult(); + } + } + + $this->indentLevel = -1; + $this->extends = []; + $this->extendsMap = []; + $this->sourceIndex = null; + $this->sourceLine = null; + $this->sourceColumn = null; + $this->env = null; + $this->scope = null; + $this->storeEnv = null; + $this->shouldEvaluate = null; + $this->ignoreCallStackMessage = false; + $this->parsedFiles = []; + $this->importedFiles = []; + $this->resolvedImports = []; + + if (!\is_null($path) && is_file($path)) { + $path = realpath($path) ?: $path; + $this->currentDirectory = dirname($path); + $this->rootDirectory = $this->currentDirectory; + } else { + $this->currentDirectory = null; + $this->rootDirectory = getcwd(); + } + + try { + $this->parser = $this->parserFactory($path); + $tree = $this->parser->parse($source); + $this->parser = null; + + $this->formatter = new $this->formatter(); + $this->rootBlock = null; + $this->rootEnv = $this->pushEnv($tree); + + $warnCallback = function ($message, $deprecation) { + $this->logger->warn($message, $deprecation); + }; + $previousWarnCallback = Warn::setCallback($warnCallback); + + try { + $this->injectVariables($this->registeredVars); + $this->compileRoot($tree); + $this->popEnv(); + } finally { + Warn::setCallback($previousWarnCallback); + } + + $sourceMapGenerator = null; + + if ($this->sourceMap) { + if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) { + $sourceMapGenerator = $this->sourceMap; + $this->sourceMap = self::SOURCE_MAP_FILE; + } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + } + } + + $out = $this->formatter->format($this->scope, $sourceMapGenerator); + + $prefix = ''; + + if ($this->charset && strlen($out) !== Util::mbStrlen($out)) { + $prefix = '@charset "UTF-8";' . "\n"; + $out = $prefix . $out; + } + + $sourceMap = null; + + if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMap = $sourceMapGenerator->generateJson($prefix); + $sourceMapUrl = null; + + switch ($this->sourceMap) { + case self::SOURCE_MAP_INLINE: + $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap)); + break; + + case self::SOURCE_MAP_FILE: + if (isset($this->sourceMapOptions['sourceMapURL'])) { + $sourceMapUrl = $this->sourceMapOptions['sourceMapURL']; + } + break; + } + + if ($sourceMapUrl !== null) { + $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl); + } + } + } catch (SassScriptException $e) { + throw new CompilerException($this->addLocationToMessage($e->getMessage()), 0, $e); + } + + $includedFiles = []; + + foreach ($this->resolvedImports as $resolvedImport) { + $includedFiles[$resolvedImport['filePath']] = $resolvedImport['filePath']; + } + + $result = new CompilationResult($out, $sourceMap, array_values($includedFiles)); + + if ($this->cache && isset($cacheKey) && isset($compileOptions)) { + $this->cache->setCache('compile', $cacheKey, new CachedResult($result, $this->parsedFiles, $this->resolvedImports), $compileOptions); + } + + // Reset state to free memory + // TODO in 2.0, reset parsedFiles as well when the getter is removed. + $this->resolvedImports = []; + $this->importedFiles = []; + + return $result; + } + + /** + * @param CachedResult $result + * + * @return bool + */ + private function isFreshCachedResult(CachedResult $result) + { + // check if any dependency file changed since the result was compiled + foreach ($result->getParsedFiles() as $file => $mtime) { + if (! is_file($file) || filemtime($file) !== $mtime) { + return false; + } + } + + if ($this->cacheCheckImportResolutions) { + $resolvedImports = []; + + foreach ($result->getResolvedImports() as $import) { + $currentDir = $import['currentDir']; + $path = $import['path']; + // store the check across all the results in memory to avoid multiple findImport() on the same path + // with same context. + // this is happening in a same hit with multiple compilations (especially with big frameworks) + if (empty($resolvedImports[$currentDir][$path])) { + $resolvedImports[$currentDir][$path] = $this->findImport($path, $currentDir); + } + + if ($resolvedImports[$currentDir][$path] !== $import['filePath']) { + return false; + } + } + } + + return true; + } + + /** + * Instantiate parser + * + * @param string|null $path + * + * @return \ScssPhp\ScssPhp\Parser + */ + protected function parserFactory($path) + { + // https://sass-lang.com/documentation/at-rules/import + // CSS files imported by Sass don’t allow any special Sass features. + // In order to make sure authors don’t accidentally write Sass in their CSS, + // all Sass features that aren’t also valid CSS will produce errors. + // Otherwise, the CSS will be rendered as-is. It can even be extended! + $cssOnly = false; + + if ($path !== null && substr($path, -4) === '.css') { + $cssOnly = true; + } + + $parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly, $this->logger); + + $this->sourceNames[] = $path; + $this->addParsedFile($path); + + return $parser; + } + + /** + * Is self extend? + * + * @param array $target + * @param array $origin + * + * @return boolean + */ + protected function isSelfExtend($target, $origin) + { + foreach ($origin as $sel) { + if (\in_array($target, $sel)) { + return true; + } + } + + return false; + } + + /** + * Push extends + * + * @param array $target + * @param array $origin + * @param array|null $block + * + * @return void + */ + protected function pushExtends($target, $origin, $block) + { + $i = \count($this->extends); + $this->extends[] = [$target, $origin, $block]; + + foreach ($target as $part) { + if (isset($this->extendsMap[$part])) { + $this->extendsMap[$part][] = $i; + } else { + $this->extendsMap[$part] = [$i]; + } + } + } + + /** + * Make output block + * + * @param string|null $type + * @param string[]|null $selectors + * + * @return \ScssPhp\ScssPhp\Formatter\OutputBlock + */ + protected function makeOutputBlock($type, $selectors = null) + { + $out = new OutputBlock(); + $out->type = $type; + $out->lines = []; + $out->children = []; + $out->parent = $this->scope; + $out->selectors = $selectors; + $out->depth = $this->env->depth; + + if ($this->env->block instanceof Block) { + $out->sourceName = $this->env->block->sourceName; + $out->sourceLine = $this->env->block->sourceLine; + $out->sourceColumn = $this->env->block->sourceColumn; + } else { + $out->sourceName = null; + $out->sourceLine = null; + $out->sourceColumn = null; + } + + return $out; + } + + /** + * Compile root + * + * @param \ScssPhp\ScssPhp\Block $rootBlock + * + * @return void + */ + protected function compileRoot(Block $rootBlock) + { + $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT); + + $this->compileChildrenNoReturn($rootBlock->children, $this->scope); + $this->flattenSelectors($this->scope); + $this->missingSelectors(); + } + + /** + * Report missing selectors + * + * @return void + */ + protected function missingSelectors() + { + foreach ($this->extends as $extend) { + if (isset($extend[3])) { + continue; + } + + list($target, $origin, $block) = $extend; + + // ignore if !optional + if ($block[2]) { + continue; + } + + $target = implode(' ', $target); + $origin = $this->collapseSelectors($origin); + + $this->sourceLine = $block[Parser::SOURCE_LINE]; + throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found."); + } + } + + /** + * Flatten selectors + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * @param string $parentKey + * + * @return void + */ + protected function flattenSelectors(OutputBlock $block, $parentKey = null) + { + if ($block->selectors) { + $selectors = []; + + foreach ($block->selectors as $s) { + $selectors[] = $s; + + if (! \is_array($s)) { + continue; + } + + // check extends + if (! empty($this->extendsMap)) { + $this->matchExtends($s, $selectors); + + // remove duplicates + array_walk($selectors, function (&$value) { + $value = serialize($value); + }); + + $selectors = array_unique($selectors); + + array_walk($selectors, function (&$value) { + $value = unserialize($value); + }); + } + } + + $block->selectors = []; + $placeholderSelector = false; + + foreach ($selectors as $selector) { + if ($this->hasSelectorPlaceholder($selector)) { + $placeholderSelector = true; + continue; + } + + $block->selectors[] = $this->compileSelector($selector); + } + + if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) { + unset($block->parent->children[$parentKey]); + + return; + } + } + + foreach ($block->children as $key => $child) { + $this->flattenSelectors($child, $key); + } + } + + /** + * Glue parts of :not( or :nth-child( ... that are in general split in selectors parts + * + * @param array $parts + * + * @return array + */ + protected function glueFunctionSelectors($parts) + { + $new = []; + + foreach ($parts as $part) { + if (\is_array($part)) { + $part = $this->glueFunctionSelectors($part); + $new[] = $part; + } else { + // a selector part finishing with a ) is the last part of a :not( or :nth-child( + // and need to be joined to this + if ( + \count($new) && \is_string($new[\count($new) - 1]) && + \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false + ) { + while (\count($new) > 1 && substr($new[\count($new) - 1], -1) !== '(') { + $part = array_pop($new) . $part; + } + $new[\count($new) - 1] .= $part; + } else { + $new[] = $part; + } + } + } + + return $new; + } + + /** + * Match extends + * + * @param array $selector + * @param array $out + * @param integer $from + * @param boolean $initial + * + * @return void + */ + protected function matchExtends($selector, &$out, $from = 0, $initial = true) + { + static $partsPile = []; + $selector = $this->glueFunctionSelectors($selector); + + if (\count($selector) == 1 && \in_array(reset($selector), $partsPile)) { + return; + } + + $outRecurs = []; + + foreach ($selector as $i => $part) { + if ($i < $from) { + continue; + } + + // check that we are not building an infinite loop of extensions + // if the new part is just including a previous part don't try to extend anymore + if (\count($part) > 1) { + foreach ($partsPile as $previousPart) { + if (! \count(array_diff($previousPart, $part))) { + continue 2; + } + } + } + + $partsPile[] = $part; + + if ($this->matchExtendsSingle($part, $origin, $initial)) { + $after = \array_slice($selector, $i + 1); + $before = \array_slice($selector, 0, $i); + list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before); + + foreach ($origin as $new) { + $k = 0; + + // remove shared parts + if (\count($new) > 1) { + while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) { + $k++; + } + } + + if (\count($nonBreakableBefore) && $k === \count($new)) { + $k--; + } + + $replacement = []; + $tempReplacement = $k > 0 ? \array_slice($new, $k) : $new; + + for ($l = \count($tempReplacement) - 1; $l >= 0; $l--) { + $slice = []; + + foreach ($tempReplacement[$l] as $chunk) { + if (! \in_array($chunk, $slice)) { + $slice[] = $chunk; + } + } + + array_unshift($replacement, $slice); + + if (! $this->isImmediateRelationshipCombinator(end($slice))) { + break; + } + } + + $afterBefore = $l != 0 ? \array_slice($tempReplacement, 0, $l) : []; + + // Merge shared direct relationships. + $mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore); + + $result = array_merge( + $before, + $mergedBefore, + $replacement, + $after + ); + + if ($result === $selector) { + continue; + } + + $this->pushOrMergeExtentedSelector($out, $result); + + // recursively check for more matches + $startRecurseFrom = \count($before) + min(\count($nonBreakableBefore), \count($mergedBefore)); + + if (\count($origin) > 1) { + $this->matchExtends($result, $out, $startRecurseFrom, false); + } else { + $this->matchExtends($result, $outRecurs, $startRecurseFrom, false); + } + + // selector sequence merging + if (! empty($before) && \count($new) > 1) { + $preSharedParts = $k > 0 ? \array_slice($before, 0, $k) : []; + $postSharedParts = $k > 0 ? \array_slice($before, $k) : $before; + + list($betweenSharedParts, $nonBreakabl2) = $this->extractRelationshipFromFragment($afterBefore); + + $result2 = array_merge( + $preSharedParts, + $betweenSharedParts, + $postSharedParts, + $nonBreakabl2, + $nonBreakableBefore, + $replacement, + $after + ); + + $this->pushOrMergeExtentedSelector($out, $result2); + } + } + } + array_pop($partsPile); + } + + while (\count($outRecurs)) { + $result = array_shift($outRecurs); + $this->pushOrMergeExtentedSelector($out, $result); + } + } + + /** + * Test a part for being a pseudo selector + * + * @param string $part + * @param array $matches + * + * @return boolean + */ + protected function isPseudoSelector($part, &$matches) + { + if ( + strpos($part, ':') === 0 && + preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches) + ) { + return true; + } + + return false; + } + + /** + * Push extended selector except if + * - this is a pseudo selector + * - same as previous + * - in a white list + * in this case we merge the pseudo selector content + * + * @param array $out + * @param array $extended + * + * @return void + */ + protected function pushOrMergeExtentedSelector(&$out, $extended) + { + if (\count($out) && \count($extended) === 1 && \count(reset($extended)) === 1) { + $single = reset($extended); + $part = reset($single); + + if ( + $this->isPseudoSelector($part, $matchesExtended) && + \in_array($matchesExtended[1], [ 'slotted' ]) + ) { + $prev = end($out); + $prev = $this->glueFunctionSelectors($prev); + + if (\count($prev) === 1 && \count(reset($prev)) === 1) { + $single = reset($prev); + $part = reset($single); + + if ( + $this->isPseudoSelector($part, $matchesPrev) && + $matchesPrev[1] === $matchesExtended[1] + ) { + $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2); + $extended[1] = $matchesPrev[2] . ', ' . $extended[1]; + $extended = implode($matchesExtended[1] . '(', $extended); + $extended = [ [ $extended ]]; + array_pop($out); + } + } + } + } + $out[] = $extended; + } + + /** + * Match extends single + * + * @param array $rawSingle + * @param array $outOrigin + * @param boolean $initial + * + * @return boolean + */ + protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true) + { + $counts = []; + $single = []; + + // simple usual cases, no need to do the whole trick + if (\in_array($rawSingle, [['>'],['+'],['~']])) { + return false; + } + + foreach ($rawSingle as $part) { + // matches Number + if (! \is_string($part)) { + return false; + } + + if (! preg_match('/^[\[.:#%]/', $part) && \count($single)) { + $single[\count($single) - 1] .= $part; + } else { + $single[] = $part; + } + } + + $extendingDecoratedTag = false; + + if (\count($single) > 1) { + $matches = null; + $extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false; + } + + $outOrigin = []; + $found = false; + + foreach ($single as $k => $part) { + if (isset($this->extendsMap[$part])) { + foreach ($this->extendsMap[$part] as $idx) { + $counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1; + } + } + + if ( + $initial && + $this->isPseudoSelector($part, $matches) && + ! \in_array($matches[1], [ 'not' ]) + ) { + $buffer = $matches[2]; + $parser = $this->parserFactory(__METHOD__); + + if ($parser->parseSelector($buffer, $subSelectors, false)) { + foreach ($subSelectors as $ksub => $subSelector) { + $subExtended = []; + $this->matchExtends($subSelector, $subExtended, 0, false); + + if ($subExtended) { + $subSelectorsExtended = $subSelectors; + $subSelectorsExtended[$ksub] = $subExtended; + + foreach ($subSelectorsExtended as $ksse => $sse) { + $subSelectorsExtended[$ksse] = $this->collapseSelectors($sse); + } + + $subSelectorsExtended = implode(', ', $subSelectorsExtended); + $singleExtended = $single; + $singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part); + $outOrigin[] = [ $singleExtended ]; + $found = true; + } + } + } + } + } + + foreach ($counts as $idx => $count) { + list($target, $origin, /* $block */) = $this->extends[$idx]; + + $origin = $this->glueFunctionSelectors($origin); + + // check count + if ($count !== \count($target)) { + continue; + } + + $this->extends[$idx][3] = true; + + $rem = array_diff($single, $target); + + foreach ($origin as $j => $new) { + // prevent infinite loop when target extends itself + if ($this->isSelfExtend($single, $origin) && ! $initial) { + return false; + } + + $replacement = end($new); + + // Extending a decorated tag with another tag is not possible. + if ( + $extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag && + preg_match('/^[a-z0-9]+$/i', $replacement[0]) + ) { + unset($origin[$j]); + continue; + } + + $combined = $this->combineSelectorSingle($replacement, $rem); + + if (\count(array_diff($combined, $origin[$j][\count($origin[$j]) - 1]))) { + $origin[$j][\count($origin[$j]) - 1] = $combined; + } + } + + $outOrigin = array_merge($outOrigin, $origin); + + $found = true; + } + + return $found; + } + + /** + * Extract a relationship from the fragment. + * + * When extracting the last portion of a selector we will be left with a + * fragment which may end with a direction relationship combinator. This + * method will extract the relationship fragment and return it along side + * the rest. + * + * @param array $fragment The selector fragment maybe ending with a direction relationship combinator. + * + * @return array The selector without the relationship fragment if any, the relationship fragment. + */ + protected function extractRelationshipFromFragment(array $fragment) + { + $parents = []; + $children = []; + + $j = $i = \count($fragment); + + for (;;) { + $children = $j != $i ? \array_slice($fragment, $j, $i - $j) : []; + $parents = \array_slice($fragment, 0, $j); + $slice = end($parents); + + if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) { + break; + } + + $j -= 2; + } + + return [$parents, $children]; + } + + /** + * Combine selector single + * + * @param array $base + * @param array $other + * + * @return array + */ + protected function combineSelectorSingle($base, $other) + { + $tag = []; + $out = []; + $wasTag = false; + $pseudo = []; + + while (\count($other) && strpos(end($other), ':') === 0) { + array_unshift($pseudo, array_pop($other)); + } + + foreach ([array_reverse($base), array_reverse($other)] as $single) { + $rang = count($single); + + foreach ($single as $part) { + if (preg_match('/^[\[:]/', $part)) { + $out[] = $part; + $wasTag = false; + } elseif (preg_match('/^[\.#]/', $part)) { + array_unshift($out, $part); + $wasTag = false; + } elseif (preg_match('/^[^_-]/', $part) && $rang === 1) { + $tag[] = $part; + $wasTag = true; + } elseif ($wasTag) { + $tag[\count($tag) - 1] .= $part; + } else { + array_unshift($out, $part); + } + $rang--; + } + } + + if (\count($tag)) { + array_unshift($out, $tag[0]); + } + + while (\count($pseudo)) { + $out[] = array_shift($pseudo); + } + + return $out; + } + + /** + * Compile media + * + * @param \ScssPhp\ScssPhp\Block $media + * + * @return void + */ + protected function compileMedia(Block $media) + { + $this->pushEnv($media); + + $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env)); + + if (! empty($mediaQueries)) { + $previousScope = $this->scope; + $parentScope = $this->mediaParent($this->scope); + + foreach ($mediaQueries as $mediaQuery) { + $this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]); + + $parentScope->children[] = $this->scope; + $parentScope = $this->scope; + } + + // top level properties in a media cause it to be wrapped + $needsWrap = false; + + foreach ($media->children as $child) { + $type = $child[0]; + + if ( + $type !== Type::T_BLOCK && + $type !== Type::T_MEDIA && + $type !== Type::T_DIRECTIVE && + $type !== Type::T_IMPORT + ) { + $needsWrap = true; + break; + } + } + + if ($needsWrap) { + $wrapped = new Block(); + $wrapped->sourceName = $media->sourceName; + $wrapped->sourceIndex = $media->sourceIndex; + $wrapped->sourceLine = $media->sourceLine; + $wrapped->sourceColumn = $media->sourceColumn; + $wrapped->selectors = []; + $wrapped->comments = []; + $wrapped->parent = $media; + $wrapped->children = $media->children; + + $media->children = [[Type::T_BLOCK, $wrapped]]; + } + + $this->compileChildrenNoReturn($media->children, $this->scope); + + $this->scope = $previousScope; + } + + $this->popEnv(); + } + + /** + * Media parent + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope + * + * @return \ScssPhp\ScssPhp\Formatter\OutputBlock + */ + protected function mediaParent(OutputBlock $scope) + { + while (! empty($scope->parent)) { + if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) { + break; + } + + $scope = $scope->parent; + } + + return $scope; + } + + /** + * Compile directive + * + * @param \ScssPhp\ScssPhp\Block|array $directive + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * + * @return void + */ + protected function compileDirective($directive, OutputBlock $out) + { + if (\is_array($directive)) { + $directiveName = $this->compileDirectiveName($directive[0]); + $s = '@' . $directiveName; + + if (! empty($directive[1])) { + $s .= ' ' . $this->compileValue($directive[1]); + } + // sass-spec compliance on newline after directives, a bit tricky :/ + $appendNewLine = (! empty($directive[2]) || strpos($s, "\n")) ? "\n" : ""; + if (\is_array($directive[0]) && empty($directive[1])) { + $appendNewLine = "\n"; + } + + if (empty($directive[3])) { + $this->appendRootDirective($s . ';' . $appendNewLine, $out, [Type::T_COMMENT, Type::T_DIRECTIVE]); + } else { + $this->appendOutputLine($out, Type::T_DIRECTIVE, $s . ';'); + } + } else { + $directive->name = $this->compileDirectiveName($directive->name); + $s = '@' . $directive->name; + + if (! empty($directive->value)) { + $s .= ' ' . $this->compileValue($directive->value); + } + + if ($directive->name === 'keyframes' || substr($directive->name, -10) === '-keyframes') { + $this->compileKeyframeBlock($directive, [$s]); + } else { + $this->compileNestedBlock($directive, [$s]); + } + } + } + + /** + * directive names can include some interpolation + * + * @param string|array $directiveName + * @return string + * @throws CompilerException + */ + protected function compileDirectiveName($directiveName) + { + if (is_string($directiveName)) { + return $directiveName; + } + + return $this->compileValue($directiveName); + } + + /** + * Compile at-root + * + * @param \ScssPhp\ScssPhp\Block $block + * + * @return void + */ + protected function compileAtRoot(Block $block) + { + $env = $this->pushEnv($block); + $envs = $this->compactEnv($env); + list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null); + + // wrap inline selector + if ($block->selector) { + $wrapped = new Block(); + $wrapped->sourceName = $block->sourceName; + $wrapped->sourceIndex = $block->sourceIndex; + $wrapped->sourceLine = $block->sourceLine; + $wrapped->sourceColumn = $block->sourceColumn; + $wrapped->selectors = $block->selector; + $wrapped->comments = []; + $wrapped->parent = $block; + $wrapped->children = $block->children; + $wrapped->selfParent = $block->selfParent; + + $block->children = [[Type::T_BLOCK, $wrapped]]; + $block->selector = null; + } + + $selfParent = $block->selfParent; + assert($selfParent !== null, 'at-root blocks must have a selfParent set.'); + + if ( + ! $selfParent->selectors && + isset($block->parent) && $block->parent && + isset($block->parent->selectors) && $block->parent->selectors + ) { + $selfParent = $block->parent; + } + + $this->env = $this->filterWithWithout($envs, $with, $without); + + $saveScope = $this->scope; + $this->scope = $this->filterScopeWithWithout($saveScope, $with, $without); + + // propagate selfParent to the children where they still can be useful + $this->compileChildrenNoReturn($block->children, $this->scope, $selfParent); + + $this->scope = $this->completeScope($this->scope, $saveScope); + $this->scope = $saveScope; + $this->env = $this->extractEnv($envs); + + $this->popEnv(); + } + + /** + * Filter at-root scope depending of with/without option + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope + * @param array $with + * @param array $without + * + * @return OutputBlock + */ + protected function filterScopeWithWithout($scope, $with, $without) + { + $filteredScopes = []; + $childStash = []; + + if ($scope->type === Type::T_ROOT) { + return $scope; + } + + // start from the root + while ($scope->parent && $scope->parent->type !== Type::T_ROOT) { + array_unshift($childStash, $scope); + $scope = $scope->parent; + } + + for (;;) { + if (! $scope) { + break; + } + + if ($this->isWith($scope, $with, $without)) { + $s = clone $scope; + $s->children = []; + $s->lines = []; + $s->parent = null; + + if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) { + $s->selectors = []; + } + + $filteredScopes[] = $s; + } + + if (\count($childStash)) { + $scope = array_shift($childStash); + } elseif ($scope->children) { + $scope = end($scope->children); + } else { + $scope = null; + } + } + + if (! \count($filteredScopes)) { + return $this->rootBlock; + } + + $newScope = array_shift($filteredScopes); + $newScope->parent = $this->rootBlock; + + $this->rootBlock->children[] = $newScope; + + $p = &$newScope; + + while (\count($filteredScopes)) { + $s = array_shift($filteredScopes); + $s->parent = $p; + $p->children[] = $s; + $newScope = &$p->children[0]; + $p = &$p->children[0]; + } + + return $newScope; + } + + /** + * found missing selector from a at-root compilation in the previous scope + * (if at-root is just enclosing a property, the selector is in the parent tree) + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope + * + * @return OutputBlock + */ + protected function completeScope($scope, $previousScope) + { + if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) { + $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth); + } + + if ($scope->children) { + foreach ($scope->children as $k => $c) { + $scope->children[$k] = $this->completeScope($c, $previousScope); + } + } + + return $scope; + } + + /** + * Find a selector by the depth node in the scope + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope + * @param integer $depth + * + * @return array + */ + protected function findScopeSelectors($scope, $depth) + { + if ($scope->depth === $depth && $scope->selectors) { + return $scope->selectors; + } + + if ($scope->children) { + foreach (array_reverse($scope->children) as $c) { + if ($s = $this->findScopeSelectors($c, $depth)) { + return $s; + } + } + } + + return []; + } + + /** + * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later + * + * @param array $withCondition + * + * @return array + */ + protected function compileWith($withCondition) + { + // just compile what we have in 2 lists + $with = []; + $without = ['rule' => true]; + + if ($withCondition) { + if ($withCondition[0] === Type::T_INTERPOLATE) { + $w = $this->compileValue($withCondition); + + $buffer = "($w)"; + $parser = $this->parserFactory(__METHOD__); + + if ($parser->parseValue($buffer, $reParsedWith)) { + $withCondition = $reParsedWith; + } + } + + if ($this->mapHasKey($withCondition, static::$with)) { + $without = []; // cancel the default + $list = $this->coerceList($this->libMapGet([$withCondition, static::$with])); + + foreach ($list[2] as $item) { + $keyword = $this->compileStringContent($this->coerceString($item)); + + $with[$keyword] = true; + } + } + + if ($this->mapHasKey($withCondition, static::$without)) { + $without = []; // cancel the default + $list = $this->coerceList($this->libMapGet([$withCondition, static::$without])); + + foreach ($list[2] as $item) { + $keyword = $this->compileStringContent($this->coerceString($item)); + + $without[$keyword] = true; + } + } + } + + return [$with, $without]; + } + + /** + * Filter env stack + * + * @param Environment[] $envs + * @param array $with + * @param array $without + * + * @return Environment + * + * @phpstan-param non-empty-array $envs + */ + protected function filterWithWithout($envs, $with, $without) + { + $filtered = []; + + foreach ($envs as $e) { + if ($e->block && ! $this->isWith($e->block, $with, $without)) { + $ec = clone $e; + $ec->block = null; + $ec->selectors = []; + + $filtered[] = $ec; + } else { + $filtered[] = $e; + } + } + + return $this->extractEnv($filtered); + } + + /** + * Filter WITH rules + * + * @param \ScssPhp\ScssPhp\Block|\ScssPhp\ScssPhp\Formatter\OutputBlock $block + * @param array $with + * @param array $without + * + * @return boolean + */ + protected function isWith($block, $with, $without) + { + if (isset($block->type)) { + if ($block->type === Type::T_MEDIA) { + return $this->testWithWithout('media', $with, $without); + } + + if ($block->type === Type::T_DIRECTIVE) { + if (isset($block->name)) { + return $this->testWithWithout($this->compileDirectiveName($block->name), $with, $without); + } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) { + return $this->testWithWithout($m[1], $with, $without); + } else { + return $this->testWithWithout('???', $with, $without); + } + } + } elseif (isset($block->selectors)) { + // a selector starting with number is a keyframe rule + if (\count($block->selectors)) { + $s = reset($block->selectors); + + while (\is_array($s)) { + $s = reset($s); + } + + if (\is_object($s) && $s instanceof Number) { + return $this->testWithWithout('keyframes', $with, $without); + } + } + + return $this->testWithWithout('rule', $with, $without); + } + + return true; + } + + /** + * Test a single type of block against with/without lists + * + * @param string $what + * @param array $with + * @param array $without + * + * @return boolean + * true if the block should be kept, false to reject + */ + protected function testWithWithout($what, $with, $without) + { + // if without, reject only if in the list (or 'all' is in the list) + if (\count($without)) { + return (isset($without[$what]) || isset($without['all'])) ? false : true; + } + + // otherwise reject all what is not in the with list + return (isset($with[$what]) || isset($with['all'])) ? true : false; + } + + + /** + * Compile keyframe block + * + * @param \ScssPhp\ScssPhp\Block $block + * @param string[] $selectors + * + * @return void + */ + protected function compileKeyframeBlock(Block $block, $selectors) + { + $env = $this->pushEnv($block); + + $envs = $this->compactEnv($env); + + $this->env = $this->extractEnv(array_filter($envs, function (Environment $e) { + return ! isset($e->block->selectors); + })); + + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->depth = 1; + $this->scope->parent->children[] = $this->scope; + + $this->compileChildrenNoReturn($block->children, $this->scope); + + $this->scope = $this->scope->parent; + $this->env = $this->extractEnv($envs); + + $this->popEnv(); + } + + /** + * Compile nested properties lines + * + * @param \ScssPhp\ScssPhp\Block $block + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * + * @return void + */ + protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out) + { + $prefix = $this->compileValue($block->prefix) . '-'; + + $nested = $this->makeOutputBlock($block->type); + $nested->parent = $out; + + if ($block->hasValue) { + $nested->depth = $out->depth + 1; + } + + $out->children[] = $nested; + + foreach ($block->children as $child) { + switch ($child[0]) { + case Type::T_ASSIGN: + array_unshift($child[1][2], $prefix); + break; + + case Type::T_NESTED_PROPERTY: + array_unshift($child[1]->prefix[2], $prefix); + break; + } + + $this->compileChild($child, $nested); + } + } + + /** + * Compile nested block + * + * @param \ScssPhp\ScssPhp\Block $block + * @param string[] $selectors + * + * @return void + */ + protected function compileNestedBlock(Block $block, $selectors) + { + $this->pushEnv($block); + + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + + // wrap assign children in a block + // except for @font-face + if ($block->type !== Type::T_DIRECTIVE || $this->compileDirectiveName($block->name) !== 'font-face') { + // need wrapping? + $needWrapping = false; + + foreach ($block->children as $child) { + if ($child[0] === Type::T_ASSIGN) { + $needWrapping = true; + break; + } + } + + if ($needWrapping) { + $wrapped = new Block(); + $wrapped->sourceName = $block->sourceName; + $wrapped->sourceIndex = $block->sourceIndex; + $wrapped->sourceLine = $block->sourceLine; + $wrapped->sourceColumn = $block->sourceColumn; + $wrapped->selectors = []; + $wrapped->comments = []; + $wrapped->parent = $block; + $wrapped->children = $block->children; + $wrapped->selfParent = $block->selfParent; + + $block->children = [[Type::T_BLOCK, $wrapped]]; + } + } + + $this->compileChildrenNoReturn($block->children, $this->scope); + + $this->scope = $this->scope->parent; + + $this->popEnv(); + } + + /** + * Recursively compiles a block. + * + * A block is analogous to a CSS block in most cases. A single SCSS document + * is encapsulated in a block when parsed, but it does not have parent tags + * so all of its children appear on the root level when compiled. + * + * Blocks are made up of selectors and children. + * + * The children of a block are just all the blocks that are defined within. + * + * Compiling the block involves pushing a fresh environment on the stack, + * and iterating through the props, compiling each one. + * + * @see Compiler::compileChild() + * + * @param \ScssPhp\ScssPhp\Block $block + * + * @return void + */ + protected function compileBlock(Block $block) + { + $env = $this->pushEnv($block); + $env->selectors = $this->evalSelectors($block->selectors); + + $out = $this->makeOutputBlock(null); + + $this->scope->children[] = $out; + + if (\count($block->children)) { + $out->selectors = $this->multiplySelectors($env, $block->selfParent); + + // propagate selfParent to the children where they still can be useful + $selfParentSelectors = null; + + if (isset($block->selfParent->selectors)) { + $selfParentSelectors = $block->selfParent->selectors; + $block->selfParent->selectors = $out->selectors; + } + + $this->compileChildrenNoReturn($block->children, $out, $block->selfParent); + + // and revert for the following children of the same block + if ($selfParentSelectors) { + $block->selfParent->selectors = $selfParentSelectors; + } + } + + $this->popEnv(); + } + + + /** + * Compile the value of a comment that can have interpolation + * + * @param array $value + * @param boolean $pushEnv + * + * @return string + */ + protected function compileCommentValue($value, $pushEnv = false) + { + $c = $value[1]; + + if (isset($value[2])) { + if ($pushEnv) { + $this->pushEnv(); + } + + try { + $c = $this->compileValue($value[2]); + } catch (SassScriptException $e) { + $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $this->addLocationToMessage($e->getMessage()), true); + // ignore error in comment compilation which are only interpolation + } catch (SassException $e) { + $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $e->getMessage(), true); + // ignore error in comment compilation which are only interpolation + } + + if ($pushEnv) { + $this->popEnv(); + } + } + + return $c; + } + + /** + * Compile root level comment + * + * @param array $block + * + * @return void + */ + protected function compileComment($block) + { + $out = $this->makeOutputBlock(Type::T_COMMENT); + $out->lines[] = $this->compileCommentValue($block, true); + + $this->scope->children[] = $out; + } + + /** + * Evaluate selectors + * + * @param array $selectors + * + * @return array + */ + protected function evalSelectors($selectors) + { + $this->shouldEvaluate = false; + + $selectors = array_map([$this, 'evalSelector'], $selectors); + + // after evaluating interpolates, we might need a second pass + if ($this->shouldEvaluate) { + $selectors = $this->replaceSelfSelector($selectors, '&'); + $buffer = $this->collapseSelectors($selectors); + $parser = $this->parserFactory(__METHOD__); + + try { + $isValid = $parser->parseSelector($buffer, $newSelectors, true); + } catch (ParserException $e) { + throw $this->error($e->getMessage()); + } + + if ($isValid) { + $selectors = array_map([$this, 'evalSelector'], $newSelectors); + } + } + + return $selectors; + } + + /** + * Evaluate selector + * + * @param array $selector + * + * @return array + */ + protected function evalSelector($selector) + { + return array_map([$this, 'evalSelectorPart'], $selector); + } + + /** + * Evaluate selector part; replaces all the interpolates, stripping quotes + * + * @param array $part + * + * @return array + */ + protected function evalSelectorPart($part) + { + foreach ($part as &$p) { + if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) { + $p = $this->compileValue($p); + + // force re-evaluation if self char or non standard char + if (preg_match(',[^\w-],', $p)) { + $this->shouldEvaluate = true; + } + } elseif ( + \is_string($p) && \strlen($p) >= 2 && + ($first = $p[0]) && ($first === '"' || $first === "'") && + substr($p, -1) === $first + ) { + $p = substr($p, 1, -1); + } + } + + return $this->flattenSelectorSingle($part); + } + + /** + * Collapse selectors + * + * @param array $selectors + * + * @return string + */ + protected function collapseSelectors($selectors) + { + $parts = []; + + foreach ($selectors as $selector) { + $output = []; + + foreach ($selector as $node) { + $compound = ''; + + array_walk_recursive( + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } + ); + + $output[] = $compound; + } + + $parts[] = implode(' ', $output); + } + + return implode(', ', $parts); + } + + /** + * Collapse selectors + * + * @param array $selectors + * + * @return array + */ + private function collapseSelectorsAsList($selectors) + { + $parts = []; + + foreach ($selectors as $selector) { + $output = []; + $glueNext = false; + + foreach ($selector as $node) { + $compound = ''; + + array_walk_recursive( + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } + ); + + if ($this->isImmediateRelationshipCombinator($compound)) { + if (\count($output)) { + $output[\count($output) - 1] .= ' ' . $compound; + } else { + $output[] = $compound; + } + + $glueNext = true; + } elseif ($glueNext) { + $output[\count($output) - 1] .= ' ' . $compound; + $glueNext = false; + } else { + $output[] = $compound; + } + } + + foreach ($output as &$o) { + $o = [Type::T_STRING, '', [$o]]; + } + + $parts[] = [Type::T_LIST, ' ', $output]; + } + + return [Type::T_LIST, ',', $parts]; + } + + /** + * Parse down the selector and revert [self] to "&" before a reparsing + * + * @param array $selectors + * @param string|null $replace + * + * @return array + */ + protected function replaceSelfSelector($selectors, $replace = null) + { + foreach ($selectors as &$part) { + if (\is_array($part)) { + if ($part === [Type::T_SELF]) { + if (\is_null($replace)) { + $replace = $this->reduce([Type::T_SELF]); + $replace = $this->compileValue($replace); + } + $part = $replace; + } else { + $part = $this->replaceSelfSelector($part, $replace); + } + } + } + + return $selectors; + } + + /** + * Flatten selector single; joins together .classes and #ids + * + * @param array $single + * + * @return array + */ + protected function flattenSelectorSingle($single) + { + $joined = []; + + foreach ($single as $part) { + if ( + empty($joined) || + ! \is_string($part) || + preg_match('/[\[.:#%]/', $part) + ) { + $joined[] = $part; + continue; + } + + if (\is_array(end($joined))) { + $joined[] = $part; + } else { + $joined[\count($joined) - 1] .= $part; + } + } + + return $joined; + } + + /** + * Compile selector to string; self(&) should have been replaced by now + * + * @param string|array $selector + * + * @return string + */ + protected function compileSelector($selector) + { + if (! \is_array($selector)) { + return $selector; // media and the like + } + + return implode( + ' ', + array_map( + [$this, 'compileSelectorPart'], + $selector + ) + ); + } + + /** + * Compile selector part + * + * @param array $piece + * + * @return string + */ + protected function compileSelectorPart($piece) + { + foreach ($piece as &$p) { + if (! \is_array($p)) { + continue; + } + + switch ($p[0]) { + case Type::T_SELF: + $p = '&'; + break; + + default: + $p = $this->compileValue($p); + break; + } + } + + return implode($piece); + } + + /** + * Has selector placeholder? + * + * @param array $selector + * + * @return boolean + */ + protected function hasSelectorPlaceholder($selector) + { + if (! \is_array($selector)) { + return false; + } + + foreach ($selector as $parts) { + foreach ($parts as $part) { + if (\strlen($part) && '%' === $part[0]) { + return true; + } + } + } + + return false; + } + + /** + * @param string $name + * + * @return void + */ + protected function pushCallStack($name = '') + { + $this->callStack[] = [ + 'n' => $name, + Parser::SOURCE_INDEX => $this->sourceIndex, + Parser::SOURCE_LINE => $this->sourceLine, + Parser::SOURCE_COLUMN => $this->sourceColumn + ]; + + // infinite calling loop + if (\count($this->callStack) > 25000) { + // not displayed but you can var_dump it to deep debug + $msg = $this->callStackMessage(true, 100); + $msg = 'Infinite calling loop'; + + throw $this->error($msg); + } + } + + /** + * @return void + */ + protected function popCallStack() + { + array_pop($this->callStack); + } + + /** + * Compile children and return result + * + * @param array $stms + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * @param string $traceName + * + * @return array|Number|null + */ + protected function compileChildren($stms, OutputBlock $out, $traceName = '') + { + $this->pushCallStack($traceName); + + foreach ($stms as $stm) { + $ret = $this->compileChild($stm, $out); + + if (isset($ret)) { + $this->popCallStack(); + + return $ret; + } + } + + $this->popCallStack(); + + return null; + } + + /** + * Compile children and throw exception if unexpected `@return` + * + * @param array $stms + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * @param \ScssPhp\ScssPhp\Block $selfParent + * @param string $traceName + * + * @return void + * + * @throws \Exception + */ + protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '') + { + $this->pushCallStack($traceName); + + foreach ($stms as $stm) { + if ($selfParent && isset($stm[1]) && \is_object($stm[1]) && $stm[1] instanceof Block) { + $stm[1]->selfParent = $selfParent; + $ret = $this->compileChild($stm, $out); + $stm[1]->selfParent = null; + } elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDE, Type::T_EXTEND])) { + $stm['selfParent'] = $selfParent; + $ret = $this->compileChild($stm, $out); + unset($stm['selfParent']); + } else { + $ret = $this->compileChild($stm, $out); + } + + if (isset($ret)) { + throw $this->error('@return may only be used within a function'); + } + } + + $this->popCallStack(); + } + + + /** + * evaluate media query : compile internal value keeping the structure unchanged + * + * @param array $queryList + * + * @return array + */ + protected function evaluateMediaQuery($queryList) + { + static $parser = null; + + $outQueryList = []; + + foreach ($queryList as $kql => $query) { + $shouldReparse = false; + + foreach ($query as $kq => $q) { + for ($i = 1; $i < \count($q); $i++) { + $value = $this->compileValue($q[$i]); + + // the parser had no mean to know if media type or expression if it was an interpolation + // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type + if ( + $q[0] == Type::T_MEDIA_TYPE && + (strpos($value, '(') !== false || + strpos($value, ')') !== false || + strpos($value, ':') !== false || + strpos($value, ',') !== false) + ) { + $shouldReparse = true; + } + + $queryList[$kql][$kq][$i] = [Type::T_KEYWORD, $value]; + } + } + + if ($shouldReparse) { + if (\is_null($parser)) { + $parser = $this->parserFactory(__METHOD__); + } + + $queryString = $this->compileMediaQuery([$queryList[$kql]]); + $queryString = reset($queryString); + + if (strpos($queryString, '@media ') === 0) { + $queryString = substr($queryString, 7); + $queries = []; + + if ($parser->parseMediaQueryList($queryString, $queries)) { + $queries = $this->evaluateMediaQuery($queries[2]); + + while (\count($queries)) { + $outQueryList[] = array_shift($queries); + } + + continue; + } + } + } + + $outQueryList[] = $queryList[$kql]; + } + + return $outQueryList; + } + + /** + * Compile media query + * + * @param array $queryList + * + * @return string[] + */ + protected function compileMediaQuery($queryList) + { + $start = '@media '; + $default = trim($start); + $out = []; + $current = ''; + + foreach ($queryList as $query) { + $type = null; + $parts = []; + + $mediaTypeOnly = true; + + foreach ($query as $q) { + if ($q[0] !== Type::T_MEDIA_TYPE) { + $mediaTypeOnly = false; + break; + } + } + + foreach ($query as $q) { + switch ($q[0]) { + case Type::T_MEDIA_TYPE: + $newType = array_map([$this, 'compileValue'], \array_slice($q, 1)); + + // combining not and anything else than media type is too risky and should be avoided + if (! $mediaTypeOnly) { + if (\in_array(Type::T_NOT, $newType) || ($type && \in_array(Type::T_NOT, $type) )) { + if ($type) { + array_unshift($parts, implode(' ', array_filter($type))); + } + + if (! empty($parts)) { + if (\strlen($current)) { + $current .= $this->formatter->tagSeparator; + } + + $current .= implode(' and ', $parts); + } + + if ($current) { + $out[] = $start . $current; + } + + $current = ''; + $type = null; + $parts = []; + } + } + + if ($newType === ['all'] && $default) { + $default = $start . 'all'; + } + + // all can be safely ignored and mixed with whatever else + if ($newType !== ['all']) { + if ($type) { + $type = $this->mergeMediaTypes($type, $newType); + + if (empty($type)) { + // merge failed : ignore this query that is not valid, skip to the next one + $parts = []; + $default = ''; // if everything fail, no @media at all + continue 3; + } + } else { + $type = $newType; + } + } + break; + + case Type::T_MEDIA_EXPRESSION: + if (isset($q[2])) { + $parts[] = '(' + . $this->compileValue($q[1]) + . $this->formatter->assignSeparator + . $this->compileValue($q[2]) + . ')'; + } else { + $parts[] = '(' + . $this->compileValue($q[1]) + . ')'; + } + break; + + case Type::T_MEDIA_VALUE: + $parts[] = $this->compileValue($q[1]); + break; + } + } + + if ($type) { + array_unshift($parts, implode(' ', array_filter($type))); + } + + if (! empty($parts)) { + if (\strlen($current)) { + $current .= $this->formatter->tagSeparator; + } + + $current .= implode(' and ', $parts); + } + } + + if ($current) { + $out[] = $start . $current; + } + + // no @media type except all, and no conflict? + if (! $out && $default) { + $out[] = $default; + } + + return $out; + } + + /** + * Merge direct relationships between selectors + * + * @param array $selectors1 + * @param array $selectors2 + * + * @return array + */ + protected function mergeDirectRelationships($selectors1, $selectors2) + { + if (empty($selectors1) || empty($selectors2)) { + return array_merge($selectors1, $selectors2); + } + + $part1 = end($selectors1); + $part2 = end($selectors2); + + if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) { + return array_merge($selectors1, $selectors2); + } + + $merged = []; + + do { + $part1 = array_pop($selectors1); + $part2 = array_pop($selectors2); + + if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) { + if ($this->isImmediateRelationshipCombinator(reset($merged)[0])) { + array_unshift($merged, [$part1[0] . $part2[0]]); + $merged = array_merge($selectors1, $selectors2, $merged); + } else { + $merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged); + } + + break; + } + + array_unshift($merged, $part1); + } while (! empty($selectors1) && ! empty($selectors2)); + + return $merged; + } + + /** + * Merge media types + * + * @param array $type1 + * @param array $type2 + * + * @return array|null + */ + protected function mergeMediaTypes($type1, $type2) + { + if (empty($type1)) { + return $type2; + } + + if (empty($type2)) { + return $type1; + } + + if (\count($type1) > 1) { + $m1 = strtolower($type1[0]); + $t1 = strtolower($type1[1]); + } else { + $m1 = ''; + $t1 = strtolower($type1[0]); + } + + if (\count($type2) > 1) { + $m2 = strtolower($type2[0]); + $t2 = strtolower($type2[1]); + } else { + $m2 = ''; + $t2 = strtolower($type2[0]); + } + + if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) { + if ($t1 === $t2) { + return null; + } + + return [ + $m1 === Type::T_NOT ? $m2 : $m1, + $m1 === Type::T_NOT ? $t2 : $t1, + ]; + } + + if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) { + // CSS has no way of representing "neither screen nor print" + if ($t1 !== $t2) { + return null; + } + + return [Type::T_NOT, $t1]; + } + + if ($t1 !== $t2) { + return null; + } + + // t1 == t2, neither m1 nor m2 are "not" + return [empty($m1) ? $m2 : $m1, $t1]; + } + + /** + * Compile import; returns true if the value was something that could be imported + * + * @param array $rawPath + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * @param boolean $once + * + * @return boolean + */ + protected function compileImport($rawPath, OutputBlock $out, $once = false) + { + if ($rawPath[0] === Type::T_STRING) { + $path = $this->compileStringContent($rawPath); + + if (strpos($path, 'url(') !== 0 && $filePath = $this->findImport($path, $this->currentDirectory)) { + $this->registerImport($this->currentDirectory, $path, $filePath); + + if (! $once || ! \in_array($filePath, $this->importedFiles)) { + $this->importFile($filePath, $out); + $this->importedFiles[] = $filePath; + } + + return true; + } + + $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out); + + return false; + } + + if ($rawPath[0] === Type::T_LIST) { + // handle a list of strings + if (\count($rawPath[2]) === 0) { + return false; + } + + foreach ($rawPath[2] as $path) { + if ($path[0] !== Type::T_STRING) { + $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out); + + return false; + } + } + + foreach ($rawPath[2] as $path) { + $this->compileImport($path, $out, $once); + } + + return true; + } + + $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out); + + return false; + } + + /** + * @param array $rawPath + * @return string + * @throws CompilerException + */ + protected function compileImportPath($rawPath) + { + $path = $this->compileValue($rawPath); + + // case url() without quotes : suppress \r \n remaining in the path + // if this is a real string there can not be CR or LF char + if (strpos($path, 'url(') === 0) { + $path = str_replace(array("\r", "\n"), array('', ' '), $path); + } else { + // if this is a file name in a string, spaces should be escaped + $path = $this->reduce($rawPath); + $path = $this->escapeImportPathString($path); + $path = $this->compileValue($path); + } + + return $path; + } + + /** + * @param array $path + * @return array + * @throws CompilerException + */ + protected function escapeImportPathString($path) + { + switch ($path[0]) { + case Type::T_LIST: + foreach ($path[2] as $k => $v) { + $path[2][$k] = $this->escapeImportPathString($v); + } + break; + case Type::T_STRING: + if ($path[1]) { + $path = $this->compileValue($path); + $path = str_replace(' ', '\\ ', $path); + $path = [Type::T_KEYWORD, $path]; + } + break; + } + + return $path; + } + + /** + * Append a root directive like @import or @charset as near as the possible from the source code + * (keeping before comments, @import and @charset coming before in the source code) + * + * @param string $line + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * @param array $allowed + * + * @return void + */ + protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT]) + { + $root = $out; + + while ($root->parent) { + $root = $root->parent; + } + + $i = 0; + + while ($i < \count($root->children)) { + if (! isset($root->children[$i]->type) || ! \in_array($root->children[$i]->type, $allowed)) { + break; + } + + $i++; + } + + // remove incompatible children from the bottom of the list + $saveChildren = []; + + while ($i < \count($root->children)) { + $saveChildren[] = array_pop($root->children); + } + + // insert the directive as a comment + $child = $this->makeOutputBlock(Type::T_COMMENT); + $child->lines[] = $line; + $child->sourceName = $this->sourceNames[$this->sourceIndex]; + $child->sourceLine = $this->sourceLine; + $child->sourceColumn = $this->sourceColumn; + + $root->children[] = $child; + + // repush children + while (\count($saveChildren)) { + $root->children[] = array_pop($saveChildren); + } + } + + /** + * Append lines to the current output block: + * directly to the block or through a child if necessary + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * @param string $type + * @param string $line + * + * @return void + */ + protected function appendOutputLine(OutputBlock $out, $type, $line) + { + $outWrite = &$out; + + // check if it's a flat output or not + if (\count($out->children)) { + $lastChild = &$out->children[\count($out->children) - 1]; + + if ( + $lastChild->depth === $out->depth && + \is_null($lastChild->selectors) && + ! \count($lastChild->children) + ) { + $outWrite = $lastChild; + } else { + $nextLines = $this->makeOutputBlock($type); + $nextLines->parent = $out; + $nextLines->depth = $out->depth; + + $out->children[] = $nextLines; + $outWrite = &$nextLines; + } + } + + $outWrite->lines[] = $line; + } + + /** + * Compile child; returns a value to halt execution + * + * @param array $child + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * + * @return array|Number|null + */ + protected function compileChild($child, OutputBlock $out) + { + if (isset($child[Parser::SOURCE_LINE])) { + $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null; + $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1; + $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1; + } elseif (\is_array($child) && isset($child[1]->sourceLine)) { + $this->sourceIndex = $child[1]->sourceIndex; + $this->sourceLine = $child[1]->sourceLine; + $this->sourceColumn = $child[1]->sourceColumn; + } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) { + $this->sourceLine = $out->sourceLine; + $sourceIndex = array_search($out->sourceName, $this->sourceNames); + $this->sourceColumn = $out->sourceColumn; + + if ($sourceIndex === false) { + $sourceIndex = null; + } + $this->sourceIndex = $sourceIndex; + } + + switch ($child[0]) { + case Type::T_SCSSPHP_IMPORT_ONCE: + $rawPath = $this->reduce($child[1]); + + $this->compileImport($rawPath, $out, true); + break; + + case Type::T_IMPORT: + $rawPath = $this->reduce($child[1]); + + $this->compileImport($rawPath, $out); + break; + + case Type::T_DIRECTIVE: + $this->compileDirective($child[1], $out); + break; + + case Type::T_AT_ROOT: + $this->compileAtRoot($child[1]); + break; + + case Type::T_MEDIA: + $this->compileMedia($child[1]); + break; + + case Type::T_BLOCK: + $this->compileBlock($child[1]); + break; + + case Type::T_CHARSET: + break; + + case Type::T_CUSTOM_PROPERTY: + list(, $name, $value) = $child; + $compiledName = $this->compileValue($name); + + // if the value reduces to null from something else then + // the property should be discarded + if ($value[0] !== Type::T_NULL) { + $value = $this->reduce($value); + + if ($value[0] === Type::T_NULL || $value === static::$nullString) { + break; + } + } + + $compiledValue = $this->compileValue($value); + + $line = $this->formatter->customProperty( + $compiledName, + $compiledValue + ); + + $this->appendOutputLine($out, Type::T_ASSIGN, $line); + break; + + case Type::T_ASSIGN: + list(, $name, $value) = $child; + + if ($name[0] === Type::T_VARIABLE) { + $flags = isset($child[3]) ? $child[3] : []; + $isDefault = \in_array('!default', $flags); + $isGlobal = \in_array('!global', $flags); + + if ($isGlobal) { + $this->set($name[1], $this->reduce($value), false, $this->rootEnv, $value); + break; + } + + $shouldSet = $isDefault && + (\is_null($result = $this->get($name[1], false)) || + $result === static::$null); + + if (! $isDefault || $shouldSet) { + $this->set($name[1], $this->reduce($value), true, null, $value); + } + break; + } + + $compiledName = $this->compileValue($name); + + // handle shorthand syntaxes : size / line-height... + if (\in_array($compiledName, ['font', 'grid-row', 'grid-column', 'border-radius'])) { + if ($value[0] === Type::T_VARIABLE) { + // if the font value comes from variable, the content is already reduced + // (i.e., formulas were already calculated), so we need the original unreduced value + $value = $this->get($value[1], true, null, true); + } + + $shorthandValue=&$value; + + $shorthandDividerNeedsUnit = false; + $maxListElements = null; + $maxShorthandDividers = 1; + + switch ($compiledName) { + case 'border-radius': + $maxListElements = 4; + $shorthandDividerNeedsUnit = true; + break; + } + + if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') { + // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica" + // we need to handle the first list element + $shorthandValue=&$value[2][0]; + } + + if ($shorthandValue[0] === Type::T_EXPRESSION && $shorthandValue[1] === '/') { + $revert = true; + + if ($shorthandDividerNeedsUnit) { + $divider = $shorthandValue[3]; + + if (\is_array($divider)) { + $divider = $this->reduce($divider, true); + } + + if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) { + $revert = false; + } + } + + if ($revert) { + $shorthandValue = $this->expToString($shorthandValue); + } + } elseif ($shorthandValue[0] === Type::T_LIST) { + foreach ($shorthandValue[2] as &$item) { + if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') { + if ($maxShorthandDividers > 0) { + $revert = true; + + // if the list of values is too long, this has to be a shorthand, + // otherwise it could be a real division + if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) { + if ($shorthandDividerNeedsUnit) { + $divider = $item[3]; + + if (\is_array($divider)) { + $divider = $this->reduce($divider, true); + } + + if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) { + $revert = false; + } + } + } + + if ($revert) { + $item = $this->expToString($item); + $maxShorthandDividers--; + } + } + } + } + } + } + + // if the value reduces to null from something else then + // the property should be discarded + if ($value[0] !== Type::T_NULL) { + $value = $this->reduce($value); + + if ($value[0] === Type::T_NULL || $value === static::$nullString) { + break; + } + } + + $compiledValue = $this->compileValue($value); + + // ignore empty value + if (\strlen($compiledValue)) { + $line = $this->formatter->property( + $compiledName, + $compiledValue + ); + $this->appendOutputLine($out, Type::T_ASSIGN, $line); + } + break; + + case Type::T_COMMENT: + if ($out->type === Type::T_ROOT) { + $this->compileComment($child); + break; + } + + $line = $this->compileCommentValue($child, true); + $this->appendOutputLine($out, Type::T_COMMENT, $line); + break; + + case Type::T_MIXIN: + case Type::T_FUNCTION: + list(, $block) = $child; + // the block need to be able to go up to it's parent env to resolve vars + $block->parentEnv = $this->getStoreEnv(); + $this->set(static::$namespaces[$block->type] . $block->name, $block, true); + break; + + case Type::T_EXTEND: + foreach ($child[1] as $sel) { + $replacedSel = $this->replaceSelfSelector($sel); + + if ($replacedSel !== $sel) { + throw $this->error('Parent selectors aren\'t allowed here.'); + } + + $results = $this->evalSelectors([$sel]); + + foreach ($results as $result) { + if (\count($result) !== 1) { + throw $this->error('complex selectors may not be extended.'); + } + + // only use the first one + $result = $result[0]; + $selectors = $out->selectors; + + if (! $selectors && isset($child['selfParent'])) { + $selectors = $this->multiplySelectors($this->env, $child['selfParent']); + } + + if (\count($result) > 1) { + $replacement = implode(', ', $result); + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + + $message = <<logger->warn($message); + } + + $this->pushExtends($result, $selectors, $child); + } + } + break; + + case Type::T_IF: + list(, $if) = $child; + + if ($this->isTruthy($this->reduce($if->cond, true))) { + return $this->compileChildren($if->children, $out); + } + + foreach ($if->cases as $case) { + if ( + $case->type === Type::T_ELSE || + $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond)) + ) { + return $this->compileChildren($case->children, $out); + } + } + break; + + case Type::T_EACH: + list(, $each) = $child; + + $list = $this->coerceList($this->reduce($each->list), ',', true); + + $this->pushEnv(); + + foreach ($list[2] as $item) { + if (\count($each->vars) === 1) { + $this->set($each->vars[0], $item, true); + } else { + list(,, $values) = $this->coerceList($item); + + foreach ($each->vars as $i => $var) { + $this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true); + } + } + + $ret = $this->compileChildren($each->children, $out); + + if ($ret) { + $store = $this->env->store; + $this->popEnv(); + $this->backPropagateEnv($store, $each->vars); + + return $ret; + } + } + $store = $this->env->store; + $this->popEnv(); + $this->backPropagateEnv($store, $each->vars); + + break; + + case Type::T_WHILE: + list(, $while) = $child; + + while ($this->isTruthy($this->reduce($while->cond, true))) { + $ret = $this->compileChildren($while->children, $out); + + if ($ret) { + return $ret; + } + } + break; + + case Type::T_FOR: + list(, $for) = $child; + + $startNumber = $this->assertNumber($this->reduce($for->start, true)); + $endNumber = $this->assertNumber($this->reduce($for->end, true)); + + $start = $this->assertInteger($startNumber); + + $numeratorUnits = $startNumber->getNumeratorUnits(); + $denominatorUnits = $startNumber->getDenominatorUnits(); + + $end = $this->assertInteger($endNumber->coerce($numeratorUnits, $denominatorUnits)); + + $d = $start < $end ? 1 : -1; + + $this->pushEnv(); + + for (;;) { + if ( + (! $for->until && $start - $d == $end) || + ($for->until && $start == $end) + ) { + break; + } + + $this->set($for->var, new Number($start, $numeratorUnits, $denominatorUnits)); + $start += $d; + + $ret = $this->compileChildren($for->children, $out); + + if ($ret) { + $store = $this->env->store; + $this->popEnv(); + $this->backPropagateEnv($store, [$for->var]); + + return $ret; + } + } + + $store = $this->env->store; + $this->popEnv(); + $this->backPropagateEnv($store, [$for->var]); + + break; + + case Type::T_RETURN: + return $this->reduce($child[1], true); + + case Type::T_NESTED_PROPERTY: + $this->compileNestedPropertiesBlock($child[1], $out); + break; + + case Type::T_INCLUDE: + // including a mixin + list(, $name, $argValues, $content, $argUsing) = $child; + + $mixin = $this->get(static::$namespaces['mixin'] . $name, false); + + if (! $mixin) { + throw $this->error("Undefined mixin $name"); + } + + $callingScope = $this->getStoreEnv(); + + // push scope, apply args + $this->pushEnv(); + $this->env->depth--; + + // Find the parent selectors in the env to be able to know what '&' refers to in the mixin + // and assign this fake parent to childs + $selfParent = null; + + if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) { + $selfParent = $child['selfParent']; + } else { + $parentSelectors = $this->multiplySelectors($this->env); + + if ($parentSelectors) { + $parent = new Block(); + $parent->selectors = $parentSelectors; + + foreach ($mixin->children as $k => $child) { + if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) { + $mixin->children[$k][1]->parent = $parent; + } + } + } + } + + // clone the stored content to not have its scope spoiled by a further call to the same mixin + // i.e., recursive @include of the same mixin + if (isset($content)) { + $copyContent = clone $content; + $copyContent->scope = clone $callingScope; + + $this->setRaw(static::$namespaces['special'] . 'content', $copyContent, $this->env); + } else { + $this->setRaw(static::$namespaces['special'] . 'content', null, $this->env); + } + + // save the "using" argument list for applying it to when "@content" is invoked + if (isset($argUsing)) { + $this->setRaw(static::$namespaces['special'] . 'using', $argUsing, $this->env); + } else { + $this->setRaw(static::$namespaces['special'] . 'using', null, $this->env); + } + + if (isset($mixin->args)) { + $this->applyArguments($mixin->args, $argValues); + } + + $this->env->marker = 'mixin'; + + if (! empty($mixin->parentEnv)) { + $this->env->declarationScopeParent = $mixin->parentEnv; + } else { + throw $this->error("@mixin $name() without parentEnv"); + } + + $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . ' ' . $name); + + $this->popEnv(); + break; + + case Type::T_MIXIN_CONTENT: + $env = isset($this->storeEnv) ? $this->storeEnv : $this->env; + $content = $this->get(static::$namespaces['special'] . 'content', false, $env); + $argUsing = $this->get(static::$namespaces['special'] . 'using', false, $env); + $argContent = $child[1]; + + if (! $content) { + break; + } + + $storeEnv = $this->storeEnv; + $varsUsing = []; + + if (isset($argUsing) && isset($argContent)) { + // Get the arguments provided for the content with the names provided in the "using" argument list + $this->storeEnv = null; + $varsUsing = $this->applyArguments($argUsing, $argContent, false); + } + + // restore the scope from the @content + $this->storeEnv = $content->scope; + + // append the vars from using if any + foreach ($varsUsing as $name => $val) { + $this->set($name, $val, true, $this->storeEnv); + } + + $this->compileChildrenNoReturn($content->children, $out); + + $this->storeEnv = $storeEnv; + break; + + case Type::T_DEBUG: + list(, $value) = $child; + + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + $value = $this->compileDebugValue($value); + + $this->logger->debug("$fname:$line DEBUG: $value"); + break; + + case Type::T_WARN: + list(, $value) = $child; + + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + $value = $this->compileDebugValue($value); + + $this->logger->warn("$value\n on line $line of $fname"); + break; + + case Type::T_ERROR: + list(, $value) = $child; + + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + $value = $this->compileValue($this->reduce($value, true)); + + throw $this->error("File $fname on line $line ERROR: $value\n"); + + default: + throw $this->error("unknown child type: $child[0]"); + } + } + + /** + * Reduce expression to string + * + * @param array $exp + * @param bool $keepParens + * + * @return array + */ + protected function expToString($exp, $keepParens = false) + { + list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp; + + $content = []; + + if ($keepParens && $inParens) { + $content[] = '('; + } + + $content[] = $this->reduce($left); + + if ($whiteLeft) { + $content[] = ' '; + } + + $content[] = $op; + + if ($whiteRight) { + $content[] = ' '; + } + + $content[] = $this->reduce($right); + + if ($keepParens && $inParens) { + $content[] = ')'; + } + + return [Type::T_STRING, '', $content]; + } + + /** + * Is truthy? + * + * @param array|Number $value + * + * @return boolean + */ + public function isTruthy($value) + { + return $value !== static::$false && $value !== static::$null; + } + + /** + * Is the value a direct relationship combinator? + * + * @param string $value + * + * @return boolean + */ + protected function isImmediateRelationshipCombinator($value) + { + return $value === '>' || $value === '+' || $value === '~'; + } + + /** + * Should $value cause its operand to eval + * + * @param array $value + * + * @return boolean + */ + protected function shouldEval($value) + { + switch ($value[0]) { + case Type::T_EXPRESSION: + if ($value[1] === '/') { + return $this->shouldEval($value[2]) || $this->shouldEval($value[3]); + } + + // fall-thru + case Type::T_VARIABLE: + case Type::T_FUNCTION_CALL: + return true; + } + + return false; + } + + /** + * Reduce value + * + * @param array|Number $value + * @param boolean $inExp + * + * @return array|Number + */ + protected function reduce($value, $inExp = false) + { + if ($value instanceof Number) { + return $value; + } + + switch ($value[0]) { + case Type::T_EXPRESSION: + list(, $op, $left, $right, $inParens) = $value; + + $opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op; + $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right); + + $left = $this->reduce($left, true); + + if ($op !== 'and' && $op !== 'or') { + $right = $this->reduce($right, true); + } + + // special case: looks like css shorthand + if ( + $opName == 'div' && ! $inParens && ! $inExp && + (($right[0] !== Type::T_NUMBER && isset($right[2]) && $right[2] != '') || + ($right[0] === Type::T_NUMBER && ! $right->unitless())) + ) { + return $this->expToString($value); + } + + $left = $this->coerceForExpression($left); + $right = $this->coerceForExpression($right); + $ltype = $left[0]; + $rtype = $right[0]; + + $ucOpName = ucfirst($opName); + $ucLType = ucfirst($ltype); + $ucRType = ucfirst($rtype); + + // this tries: + // 1. op[op name][left type][right type] + // 2. op[left type][right type] (passing the op as first arg + // 3. op[op name] + $fn = "op${ucOpName}${ucLType}${ucRType}"; + + if ( + \is_callable([$this, $fn]) || + (($fn = "op${ucLType}${ucRType}") && + \is_callable([$this, $fn]) && + $passOp = true) || + (($fn = "op${ucOpName}") && + \is_callable([$this, $fn]) && + $genOp = true) + ) { + $shouldEval = $inParens || $inExp; + + if (isset($passOp)) { + $out = $this->$fn($op, $left, $right, $shouldEval); + } else { + $out = $this->$fn($left, $right, $shouldEval); + } + + if (isset($out)) { + return $out; + } + } + + return $this->expToString($value); + + case Type::T_UNARY: + list(, $op, $exp, $inParens) = $value; + + $inExp = $inExp || $this->shouldEval($exp); + $exp = $this->reduce($exp); + + if ($exp instanceof Number) { + switch ($op) { + case '+': + return $exp; + + case '-': + return $exp->unaryMinus(); + } + } + + if ($op === 'not') { + if ($inExp || $inParens) { + if ($exp === static::$false || $exp === static::$null) { + return static::$true; + } + + return static::$false; + } + + $op = $op . ' '; + } + + return [Type::T_STRING, '', [$op, $exp]]; + + case Type::T_VARIABLE: + return $this->reduce($this->get($value[1])); + + case Type::T_LIST: + foreach ($value[2] as &$item) { + $item = $this->reduce($item); + } + unset($item); + + if (isset($value[3]) && \is_array($value[3])) { + foreach ($value[3] as &$item) { + $item = $this->reduce($item); + } + unset($item); + } + + return $value; + + case Type::T_MAP: + foreach ($value[1] as &$item) { + $item = $this->reduce($item); + } + + foreach ($value[2] as &$item) { + $item = $this->reduce($item); + } + + return $value; + + case Type::T_STRING: + foreach ($value[2] as &$item) { + if (\is_array($item) || $item instanceof Number) { + $item = $this->reduce($item); + } + } + + return $value; + + case Type::T_INTERPOLATE: + $value[1] = $this->reduce($value[1]); + + if ($inExp) { + return [Type::T_KEYWORD, $this->compileValue($value, false)]; + } + + return $value; + + case Type::T_FUNCTION_CALL: + return $this->fncall($value[1], $value[2]); + + case Type::T_SELF: + $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null; + $selfSelector = $this->multiplySelectors($this->env, $selfParent); + $selfSelector = $this->collapseSelectorsAsList($selfSelector); + + return $selfSelector; + + default: + return $value; + } + } + + /** + * Function caller + * + * @param string|array $functionReference + * @param array $argValues + * + * @return array|Number + */ + protected function fncall($functionReference, $argValues) + { + // a string means this is a static hard reference coming from the parsing + if (is_string($functionReference)) { + $name = $functionReference; + + $functionReference = $this->getFunctionReference($name); + if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) { + $functionReference = [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]]; + } + } + + // a function type means we just want a plain css function call + if ($functionReference[0] === Type::T_FUNCTION) { + // for CSS functions, simply flatten the arguments into a list + $listArgs = []; + + foreach ((array) $argValues as $arg) { + if (empty($arg[0]) || count($argValues) === 1) { + $listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1])); + } + } + + return [Type::T_FUNCTION, $functionReference[1], [Type::T_LIST, ',', $listArgs]]; + } + + if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) { + return static::$defaultValue; + } + + + switch ($functionReference[1]) { + // SCSS @function + case 'scss': + return $this->callScssFunction($functionReference[3], $argValues); + + // native PHP functions + case 'user': + case 'native': + list(,,$name, $fn, $prototype) = $functionReference; + + // special cases of css valid functions min/max + $name = strtolower($name); + if (\in_array($name, ['min', 'max']) && count($argValues) >= 1) { + $cssFunction = $this->cssValidArg( + [Type::T_FUNCTION_CALL, $name, $argValues], + ['min', 'max', 'calc', 'env', 'var'] + ); + if ($cssFunction !== false) { + return $cssFunction; + } + } + $returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues); + + if (! isset($returnValue)) { + return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues); + } + + return $returnValue; + + default: + return static::$defaultValue; + } + } + + /** + * @param array|Number $arg + * @param string[] $allowed_function + * @param bool $inFunction + * + * @return array|Number|false + */ + protected function cssValidArg($arg, $allowed_function = [], $inFunction = false) + { + if ($arg instanceof Number) { + return $this->stringifyFncallArgs($arg); + } + + switch ($arg[0]) { + case Type::T_INTERPOLATE: + return [Type::T_KEYWORD, $this->CompileValue($arg)]; + + case Type::T_FUNCTION: + if (! \in_array($arg[1], $allowed_function)) { + return false; + } + if ($arg[2][0] === Type::T_LIST) { + foreach ($arg[2][2] as $k => $subarg) { + $arg[2][2][$k] = $this->cssValidArg($subarg, $allowed_function, $arg[1]); + if ($arg[2][2][$k] === false) { + return false; + } + } + } + return $arg; + + case Type::T_FUNCTION_CALL: + if (! \in_array($arg[1], $allowed_function)) { + return false; + } + $cssArgs = []; + foreach ($arg[2] as $argValue) { + if ($argValue === static::$null) { + return false; + } + $cssArg = $this->cssValidArg($argValue[1], $allowed_function, $arg[1]); + if (empty($argValue[0]) && $cssArg !== false) { + $cssArgs[] = [$argValue[0], $cssArg]; + } else { + return false; + } + } + + return $this->fncall([Type::T_FUNCTION, $arg[1], [Type::T_LIST, ',', []]], $cssArgs); + + case Type::T_STRING: + case Type::T_KEYWORD: + if (!$inFunction or !\in_array($inFunction, ['calc', 'env', 'var'])) { + return false; + } + return $this->stringifyFncallArgs($arg); + + case Type::T_LIST: + if (!$inFunction) { + return false; + } + if (empty($arg['enclosing']) and $arg[1] === '') { + foreach ($arg[2] as $k => $subarg) { + $arg[2][$k] = $this->cssValidArg($subarg, $allowed_function, $inFunction); + if ($arg[2][$k] === false) { + return false; + } + } + $arg[0] = Type::T_STRING; + return $arg; + } + return false; + + case Type::T_EXPRESSION: + if (! \in_array($arg[1], ['+', '-', '/', '*'])) { + return false; + } + $arg[2] = $this->cssValidArg($arg[2], $allowed_function, $inFunction); + $arg[3] = $this->cssValidArg($arg[3], $allowed_function, $inFunction); + if ($arg[2] === false || $arg[3] === false) { + return false; + } + return $this->expToString($arg, true); + + case Type::T_VARIABLE: + case Type::T_SELF: + default: + return false; + } + } + + + /** + * Reformat fncall arguments to proper css function output + * + * @param array|Number $arg + * + * @return array|Number + */ + protected function stringifyFncallArgs($arg) + { + if ($arg instanceof Number) { + return $arg; + } + + switch ($arg[0]) { + case Type::T_LIST: + foreach ($arg[2] as $k => $v) { + $arg[2][$k] = $this->stringifyFncallArgs($v); + } + break; + + case Type::T_EXPRESSION: + if ($arg[1] === '/') { + $arg[2] = $this->stringifyFncallArgs($arg[2]); + $arg[3] = $this->stringifyFncallArgs($arg[3]); + $arg[5] = $arg[6] = false; // no space around / + $arg = $this->expToString($arg); + } + break; + + case Type::T_FUNCTION_CALL: + $name = strtolower($arg[1]); + + if (in_array($name, ['max', 'min', 'calc'])) { + $args = $arg[2]; + $arg = $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $args); + } + break; + } + + return $arg; + } + + /** + * Find a function reference + * @param string $name + * @param bool $safeCopy + * @return array + */ + protected function getFunctionReference($name, $safeCopy = false) + { + // SCSS @function + if ($func = $this->get(static::$namespaces['function'] . $name, false)) { + if ($safeCopy) { + $func = clone $func; + } + + return [Type::T_FUNCTION_REFERENCE, 'scss', $name, $func]; + } + + // native PHP functions + + // try to find a native lib function + $normalizedName = $this->normalizeName($name); + + if (isset($this->userFunctions[$normalizedName])) { + // see if we can find a user function + list($f, $prototype) = $this->userFunctions[$normalizedName]; + + return [Type::T_FUNCTION_REFERENCE, 'user', $name, $f, $prototype]; + } + + $lowercasedName = strtolower($normalizedName); + + // Special functions overriding a CSS function are case-insensitive. We normalize them as lowercase + // to avoid the deprecation warning about the wrong case being used. + if ($lowercasedName === 'min' || $lowercasedName === 'max') { + $normalizedName = $lowercasedName; + } + + if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) { + $libName = $f[1]; + $prototype = isset(static::$$libName) ? static::$$libName : null; + + // All core functions have a prototype defined. Not finding the + // prototype can mean 2 things: + // - the function comes from a child class (deprecated just after) + // - the function was found with a different case, which relates to calling the + // wrong Sass function due to our camelCase usage (`fade-in()` vs `fadein()`), + // because PHP method names are case-insensitive while property names are + // case-sensitive. + if ($prototype === null || strtolower($normalizedName) !== $normalizedName) { + $r = new \ReflectionMethod($this, $libName); + $actualLibName = $r->name; + + if ($actualLibName !== $libName || strtolower($normalizedName) !== $normalizedName) { + $kebabCaseName = preg_replace('~(?<=\\w)([A-Z])~', '-$1', substr($actualLibName, 3)); + assert($kebabCaseName !== null); + $originalName = strtolower($kebabCaseName); + $warning = "Calling built-in functions with a non-standard name is deprecated since Scssphp 1.8.0 and will not work anymore in 2.0 (they will be treated as CSS function calls instead).\nUse \"$originalName\" instead of \"$name\"."; + @trigger_error($warning, E_USER_DEPRECATED); + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + Warn::deprecation("$warning\n on line $line of $fname"); + + // Use the actual function definition + $prototype = isset(static::$$actualLibName) ? static::$$actualLibName : null; + $f[1] = $libName = $actualLibName; + } + } + + if (\get_class($this) !== __CLASS__ && !isset($this->warnedChildFunctions[$libName])) { + $r = new \ReflectionMethod($this, $libName); + $declaringClass = $r->getDeclaringClass()->name; + + $needsWarning = $this->warnedChildFunctions[$libName] = $declaringClass !== __CLASS__; + + if ($needsWarning) { + if (method_exists(__CLASS__, $libName)) { + @trigger_error(sprintf('Overriding the "%s" core function by extending the Compiler is deprecated and will be unsupported in 2.0. Remove the "%s::%s" method.', $normalizedName, $declaringClass, $libName), E_USER_DEPRECATED); + } else { + @trigger_error(sprintf('Registering custom functions by extending the Compiler and using the lib* discovery mechanism is deprecated and will be removed in 2.0. Replace the "%s::%s" method with registering the "%s" function through "Compiler::registerFunction".', $declaringClass, $libName, $normalizedName), E_USER_DEPRECATED); + } + } + } + + return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype]; + } + + return static::$null; + } + + + /** + * Normalize name + * + * @param string $name + * + * @return string + */ + protected function normalizeName($name) + { + return str_replace('-', '_', $name); + } + + /** + * Normalize value + * + * @internal + * + * @param array|Number $value + * + * @return array|Number + */ + public function normalizeValue($value) + { + $value = $this->coerceForExpression($this->reduce($value)); + + if ($value instanceof Number) { + return $value; + } + + switch ($value[0]) { + case Type::T_LIST: + $value = $this->extractInterpolation($value); + + if ($value[0] !== Type::T_LIST) { + return [Type::T_KEYWORD, $this->compileValue($value)]; + } + + foreach ($value[2] as $key => $item) { + $value[2][$key] = $this->normalizeValue($item); + } + + if (! empty($value['enclosing'])) { + unset($value['enclosing']); + } + + return $value; + + case Type::T_STRING: + return [$value[0], '"', [$this->compileStringContent($value)]]; + + case Type::T_INTERPOLATE: + return [Type::T_KEYWORD, $this->compileValue($value)]; + + default: + return $value; + } + } + + /** + * Add numbers + * + * @param Number $left + * @param Number $right + * + * @return Number + */ + protected function opAddNumberNumber(Number $left, Number $right) + { + return $left->plus($right); + } + + /** + * Multiply numbers + * + * @param Number $left + * @param Number $right + * + * @return Number + */ + protected function opMulNumberNumber(Number $left, Number $right) + { + return $left->times($right); + } + + /** + * Subtract numbers + * + * @param Number $left + * @param Number $right + * + * @return Number + */ + protected function opSubNumberNumber(Number $left, Number $right) + { + return $left->minus($right); + } + + /** + * Divide numbers + * + * @param Number $left + * @param Number $right + * + * @return Number + */ + protected function opDivNumberNumber(Number $left, Number $right) + { + return $left->dividedBy($right); + } + + /** + * Mod numbers + * + * @param Number $left + * @param Number $right + * + * @return Number + */ + protected function opModNumberNumber(Number $left, Number $right) + { + return $left->modulo($right); + } + + /** + * Add strings + * + * @param array $left + * @param array $right + * + * @return array|null + */ + protected function opAdd($left, $right) + { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] === Type::T_STRING) { + $right[1] = ''; + } + + $strLeft[2][] = $right; + + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + if ($left[0] === Type::T_STRING) { + $left[1] = ''; + } + + array_unshift($strRight[2], $left); + + return $strRight; + } + + return null; + } + + /** + * Boolean and + * + * @param array|Number $left + * @param array|Number $right + * @param boolean $shouldEval + * + * @return array|Number|null + */ + protected function opAnd($left, $right, $shouldEval) + { + $truthy = ($left === static::$null || $right === static::$null) || + ($left === static::$false || $left === static::$true) && + ($right === static::$false || $right === static::$true); + + if (! $shouldEval) { + if (! $truthy) { + return null; + } + } + + if ($left !== static::$false && $left !== static::$null) { + return $this->reduce($right, true); + } + + return $left; + } + + /** + * Boolean or + * + * @param array|Number $left + * @param array|Number $right + * @param boolean $shouldEval + * + * @return array|Number|null + */ + protected function opOr($left, $right, $shouldEval) + { + $truthy = ($left === static::$null || $right === static::$null) || + ($left === static::$false || $left === static::$true) && + ($right === static::$false || $right === static::$true); + + if (! $shouldEval) { + if (! $truthy) { + return null; + } + } + + if ($left !== static::$false && $left !== static::$null) { + return $left; + } + + return $this->reduce($right, true); + } + + /** + * Compare colors + * + * @param string $op + * @param array $left + * @param array $right + * + * @return array + */ + protected function opColorColor($op, $left, $right) + { + if ($op !== '==' && $op !== '!=') { + $warning = "Color arithmetic is deprecated and will be an error in future versions.\n" + . "Consider using Sass's color functions instead."; + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + + Warn::deprecation("$warning\n on line $line of $fname"); + } + + $out = [Type::T_COLOR]; + + foreach ([1, 2, 3] as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + + case '-': + $out[] = $lval - $rval; + break; + + case '*': + $out[] = $lval * $rval; + break; + + case '%': + if ($rval == 0) { + throw $this->error("color: Can't take modulo by zero"); + } + + $out[] = $lval % $rval; + break; + + case '/': + if ($rval == 0) { + throw $this->error("color: Can't divide by zero"); + } + + $out[] = (int) ($lval / $rval); + break; + + case '==': + return $this->opEq($left, $right); + + case '!=': + return $this->opNeq($left, $right); + + default: + throw $this->error("color: unknown op $op"); + } + } + + if (isset($left[4])) { + $out[4] = $left[4]; + } elseif (isset($right[4])) { + $out[4] = $right[4]; + } + + return $this->fixColor($out); + } + + /** + * Compare color and number + * + * @param string $op + * @param array $left + * @param Number $right + * + * @return array + */ + protected function opColorNumber($op, $left, Number $right) + { + if ($op === '==') { + return static::$false; + } + + if ($op === '!=') { + return static::$true; + } + + $value = $right->getDimension(); + + return $this->opColorColor( + $op, + $left, + [Type::T_COLOR, $value, $value, $value] + ); + } + + /** + * Compare number and color + * + * @param string $op + * @param Number $left + * @param array $right + * + * @return array + */ + protected function opNumberColor($op, Number $left, $right) + { + if ($op === '==') { + return static::$false; + } + + if ($op === '!=') { + return static::$true; + } + + $value = $left->getDimension(); + + return $this->opColorColor( + $op, + [Type::T_COLOR, $value, $value, $value], + $right + ); + } + + /** + * Compare number1 == number2 + * + * @param array|Number $left + * @param array|Number $right + * + * @return array + */ + protected function opEq($left, $right) + { + if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) { + $lStr[1] = ''; + $rStr[1] = ''; + + $left = $this->compileValue($lStr); + $right = $this->compileValue($rStr); + } + + return $this->toBool($left === $right); + } + + /** + * Compare number1 != number2 + * + * @param array|Number $left + * @param array|Number $right + * + * @return array + */ + protected function opNeq($left, $right) + { + if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) { + $lStr[1] = ''; + $rStr[1] = ''; + + $left = $this->compileValue($lStr); + $right = $this->compileValue($rStr); + } + + return $this->toBool($left !== $right); + } + + /** + * Compare number1 == number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opEqNumberNumber(Number $left, Number $right) + { + return $this->toBool($left->equals($right)); + } + + /** + * Compare number1 != number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opNeqNumberNumber(Number $left, Number $right) + { + return $this->toBool(!$left->equals($right)); + } + + /** + * Compare number1 >= number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opGteNumberNumber(Number $left, Number $right) + { + return $this->toBool($left->greaterThanOrEqual($right)); + } + + /** + * Compare number1 > number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opGtNumberNumber(Number $left, Number $right) + { + return $this->toBool($left->greaterThan($right)); + } + + /** + * Compare number1 <= number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opLteNumberNumber(Number $left, Number $right) + { + return $this->toBool($left->lessThanOrEqual($right)); + } + + /** + * Compare number1 < number2 + * + * @param Number $left + * @param Number $right + * + * @return array + */ + protected function opLtNumberNumber(Number $left, Number $right) + { + return $this->toBool($left->lessThan($right)); + } + + /** + * Cast to boolean + * + * @api + * + * @param bool $thing + * + * @return array + */ + public function toBool($thing) + { + return $thing ? static::$true : static::$false; + } + + /** + * Escape non printable chars in strings output as in dart-sass + * + * @internal + * + * @param string $string + * @param bool $inKeyword + * + * @return string + */ + public function escapeNonPrintableChars($string, $inKeyword = false) + { + static $replacement = []; + if (empty($replacement[$inKeyword])) { + for ($i = 0; $i < 32; $i++) { + if ($i !== 9 || $inKeyword) { + $replacement[$inKeyword][chr($i)] = '\\' . dechex($i) . ($inKeyword ? ' ' : chr(0)); + } + } + } + $string = str_replace(array_keys($replacement[$inKeyword]), array_values($replacement[$inKeyword]), $string); + // chr(0) is not a possible char from the input, so any chr(0) comes from our escaping replacement + if (strpos($string, chr(0)) !== false) { + if (substr($string, -1) === chr(0)) { + $string = substr($string, 0, -1); + } + $string = str_replace( + [chr(0) . '\\',chr(0) . ' '], + [ '\\', ' '], + $string + ); + if (strpos($string, chr(0)) !== false) { + $parts = explode(chr(0), $string); + $string = array_shift($parts); + while (count($parts)) { + $next = array_shift($parts); + if (strpos("0123456789abcdefABCDEF" . chr(9), $next[0]) !== false) { + $string .= " "; + } + $string .= $next; + } + } + } + + return $string; + } + + /** + * Compiles a primitive value into a CSS property value. + * + * Values in scssphp are typed by being wrapped in arrays, their format is + * typically: + * + * array(type, contents [, additional_contents]*) + * + * The input is expected to be reduced. This function will not work on + * things like expressions and variables. + * + * @api + * + * @param array|Number $value + * @param bool $quote + * + * @return string + */ + public function compileValue($value, $quote = true) + { + $value = $this->reduce($value); + + if ($value instanceof Number) { + return $value->output($this); + } + + switch ($value[0]) { + case Type::T_KEYWORD: + return $this->escapeNonPrintableChars($value[1], true); + + case Type::T_COLOR: + // [1] - red component (either number for a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + + $r = $this->compileRGBAValue($r); + $g = $this->compileRGBAValue($g); + $b = $this->compileRGBAValue($b); + + if (\count($value) === 5) { + $alpha = $this->compileRGBAValue($value[4], true); + + if (! is_numeric($alpha) || $alpha < 1) { + $colorName = Colors::RGBaToColorName($r, $g, $b, $alpha); + + if (! \is_null($colorName)) { + return $colorName; + } + + if (is_numeric($alpha)) { + $a = new Number($alpha, ''); + } else { + $a = $alpha; + } + + return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $a . ')'; + } + } + + if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b)) { + return 'rgb(' . $r . ', ' . $g . ', ' . $b . ')'; + } + + $colorName = Colors::RGBaToColorName($r, $g, $b); + + if (! \is_null($colorName)) { + return $colorName; + } + + $h = sprintf('#%02x%02x%02x', $r, $g, $b); + + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + + return $h; + + case Type::T_STRING: + $content = $this->compileStringContent($value, $quote); + + if ($value[1] && $quote) { + $content = str_replace('\\', '\\\\', $content); + + $content = $this->escapeNonPrintableChars($content); + + // force double quote as string quote for the output in certain cases + if ( + $value[1] === "'" && + (strpos($content, '"') === false or strpos($content, "'") !== false) && + strpbrk($content, '{}\\\'') !== false + ) { + $value[1] = '"'; + } elseif ( + $value[1] === '"' && + (strpos($content, '"') !== false and strpos($content, "'") === false) + ) { + $value[1] = "'"; + } + + $content = str_replace($value[1], '\\' . $value[1], $content); + } + + return $value[1] . $content . $value[1]; + + case Type::T_FUNCTION: + $args = ! empty($value[2]) ? $this->compileValue($value[2], $quote) : ''; + + return "$value[1]($args)"; + + case Type::T_FUNCTION_REFERENCE: + $name = ! empty($value[2]) ? $value[2] : ''; + + return "get-function(\"$name\")"; + + case Type::T_LIST: + $value = $this->extractInterpolation($value); + + if ($value[0] !== Type::T_LIST) { + return $this->compileValue($value, $quote); + } + + list(, $delim, $items) = $value; + $pre = $post = ''; + + if (! empty($value['enclosing'])) { + switch ($value['enclosing']) { + case 'parent': + //$pre = '('; + //$post = ')'; + break; + case 'forced_parent': + $pre = '('; + $post = ')'; + break; + case 'bracket': + case 'forced_bracket': + $pre = '['; + $post = ']'; + break; + } + } + + $prefix_value = ''; + + if ($delim !== ' ') { + $prefix_value = ' '; + } + + $filtered = []; + + $same_string_quote = null; + foreach ($items as $item) { + if (\is_null($same_string_quote)) { + $same_string_quote = false; + if ($item[0] === Type::T_STRING) { + $same_string_quote = $item[1]; + foreach ($items as $ii) { + if ($ii[0] !== Type::T_STRING) { + $same_string_quote = false; + break; + } + } + } + } + if ($item[0] === Type::T_NULL) { + continue; + } + if ($same_string_quote === '"' && $item[0] === Type::T_STRING && $item[1]) { + $item[1] = $same_string_quote; + } + + $compiled = $this->compileValue($item, $quote); + + if ($prefix_value && \strlen($compiled)) { + $compiled = $prefix_value . $compiled; + } + + $filtered[] = $compiled; + } + + return $pre . substr(implode("$delim", $filtered), \strlen($prefix_value)) . $post; + + case Type::T_MAP: + $keys = $value[1]; + $values = $value[2]; + $filtered = []; + + for ($i = 0, $s = \count($keys); $i < $s; $i++) { + $filtered[$this->compileValue($keys[$i], $quote)] = $this->compileValue($values[$i], $quote); + } + + array_walk($filtered, function (&$value, $key) { + $value = $key . ': ' . $value; + }); + + return '(' . implode(', ', $filtered) . ')'; + + case Type::T_INTERPOLATED: + // node created by extractInterpolation + list(, $interpolate, $left, $right) = $value; + list(,, $whiteLeft, $whiteRight) = $interpolate; + + $delim = $left[1]; + + if ($delim && $delim !== ' ' && ! $whiteLeft) { + $delim .= ' '; + } + + $left = \count($left[2]) > 0 + ? $this->compileValue($left, $quote) . $delim . $whiteLeft + : ''; + + $delim = $right[1]; + + if ($delim && $delim !== ' ') { + $delim .= ' '; + } + + $right = \count($right[2]) > 0 ? + $whiteRight . $delim . $this->compileValue($right, $quote) : ''; + + return $left . $this->compileValue($interpolate, $quote) . $right; + + case Type::T_INTERPOLATE: + // strip quotes if it's a string + $reduced = $this->reduce($value[1]); + + if ($reduced instanceof Number) { + return $this->compileValue($reduced, $quote); + } + + switch ($reduced[0]) { + case Type::T_LIST: + $reduced = $this->extractInterpolation($reduced); + + if ($reduced[0] !== Type::T_LIST) { + break; + } + + list(, $delim, $items) = $reduced; + + if ($delim !== ' ') { + $delim .= ' '; + } + + $filtered = []; + + foreach ($items as $item) { + if ($item[0] === Type::T_NULL) { + continue; + } + + if ($item[0] === Type::T_STRING) { + $filtered[] = $this->compileStringContent($item, $quote); + } elseif ($item[0] === Type::T_KEYWORD) { + $filtered[] = $item[1]; + } else { + $filtered[] = $this->compileValue($item, $quote); + } + } + + $reduced = [Type::T_KEYWORD, implode("$delim", $filtered)]; + break; + + case Type::T_STRING: + $reduced = [Type::T_STRING, '', [$this->compileStringContent($reduced)]]; + break; + + case Type::T_NULL: + $reduced = [Type::T_KEYWORD, '']; + } + + return $this->compileValue($reduced, $quote); + + case Type::T_NULL: + return 'null'; + + case Type::T_COMMENT: + return $this->compileCommentValue($value); + + default: + throw $this->error('unknown value type: ' . json_encode($value)); + } + } + + /** + * @param array|Number $value + * + * @return string + */ + protected function compileDebugValue($value) + { + $value = $this->reduce($value, true); + + if ($value instanceof Number) { + return $this->compileValue($value); + } + + switch ($value[0]) { + case Type::T_STRING: + return $this->compileStringContent($value); + + default: + return $this->compileValue($value); + } + } + + /** + * Flatten list + * + * @param array $list + * + * @return string + * + * @deprecated + */ + protected function flattenList($list) + { + @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED); + + return $this->compileValue($list); + } + + /** + * Gets the text of a Sass string + * + * Calling this method on anything else than a SassString is unsupported. Use {@see assertString} first + * to ensure that the value is indeed a string. + * + * @param array $value + * + * @return string + */ + public function getStringText(array $value) + { + if ($value[0] !== Type::T_STRING) { + throw new \InvalidArgumentException('The argument is not a sass string. Did you forgot to use "assertString"?'); + } + + return $this->compileStringContent($value); + } + + /** + * Compile string content + * + * @param array $string + * @param bool $quote + * + * @return string + */ + protected function compileStringContent($string, $quote = true) + { + $parts = []; + + foreach ($string[2] as $part) { + if (\is_array($part) || $part instanceof Number) { + $parts[] = $this->compileValue($part, $quote); + } else { + $parts[] = $part; + } + } + + return implode($parts); + } + + /** + * Extract interpolation; it doesn't need to be recursive, compileValue will handle that + * + * @param array $list + * + * @return array + */ + protected function extractInterpolation($list) + { + $items = $list[2]; + + foreach ($items as $i => $item) { + if ($item[0] === Type::T_INTERPOLATE) { + $before = [Type::T_LIST, $list[1], \array_slice($items, 0, $i)]; + $after = [Type::T_LIST, $list[1], \array_slice($items, $i + 1)]; + + return [Type::T_INTERPOLATED, $item, $before, $after]; + } + } + + return $list; + } + + /** + * Find the final set of selectors + * + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param \ScssPhp\ScssPhp\Block $selfParent + * + * @return array + */ + protected function multiplySelectors(Environment $env, $selfParent = null) + { + $envs = $this->compactEnv($env); + $selectors = []; + $parentSelectors = [[]]; + + $selfParentSelectors = null; + + if (! \is_null($selfParent) && $selfParent->selectors) { + $selfParentSelectors = $this->evalSelectors($selfParent->selectors); + } + + while ($env = array_pop($envs)) { + if (empty($env->selectors)) { + continue; + } + + $selectors = $env->selectors; + + do { + $stillHasSelf = false; + $prevSelectors = $selectors; + $selectors = []; + + foreach ($parentSelectors as $parent) { + foreach ($prevSelectors as $selector) { + if ($selfParentSelectors) { + foreach ($selfParentSelectors as $selfParent) { + // if no '&' in the selector, each call will give same result, only add once + $s = $this->joinSelectors($parent, $selector, $stillHasSelf, $selfParent); + $selectors[serialize($s)] = $s; + } + } else { + $s = $this->joinSelectors($parent, $selector, $stillHasSelf); + $selectors[serialize($s)] = $s; + } + } + } + } while ($stillHasSelf); + + $parentSelectors = $selectors; + } + + $selectors = array_values($selectors); + + // case we are just starting a at-root : nothing to multiply but parentSelectors + if (! $selectors && $selfParentSelectors) { + $selectors = $selfParentSelectors; + } + + return $selectors; + } + + /** + * Join selectors; looks for & to replace, or append parent before child + * + * @param array $parent + * @param array $child + * @param boolean $stillHasSelf + * @param array $selfParentSelectors + + * @return array + */ + protected function joinSelectors($parent, $child, &$stillHasSelf, $selfParentSelectors = null) + { + $setSelf = false; + $out = []; + + foreach ($child as $part) { + $newPart = []; + + foreach ($part as $p) { + // only replace & once and should be recalled to be able to make combinations + if ($p === static::$selfSelector && $setSelf) { + $stillHasSelf = true; + } + + if ($p === static::$selfSelector && ! $setSelf) { + $setSelf = true; + + if (\is_null($selfParentSelectors)) { + $selfParentSelectors = $parent; + } + + foreach ($selfParentSelectors as $i => $parentPart) { + if ($i > 0) { + $out[] = $newPart; + $newPart = []; + } + + foreach ($parentPart as $pp) { + if (\is_array($pp)) { + $flatten = []; + + array_walk_recursive($pp, function ($a) use (&$flatten) { + $flatten[] = $a; + }); + + $pp = implode($flatten); + } + + $newPart[] = $pp; + } + } + } else { + $newPart[] = $p; + } + } + + $out[] = $newPart; + } + + return $setSelf ? $out : array_merge($parent, $child); + } + + /** + * Multiply media + * + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param array $childQueries + * + * @return array + */ + protected function multiplyMedia(Environment $env = null, $childQueries = null) + { + if ( + ! isset($env) || + ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA + ) { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $parentQueries = isset($env->block->queryList) + ? $env->block->queryList + : [[[Type::T_MEDIA_VALUE, $env->block->value]]]; + + $store = [$this->env, $this->storeEnv]; + + $this->env = $env; + $this->storeEnv = null; + $parentQueries = $this->evaluateMediaQuery($parentQueries); + + list($this->env, $this->storeEnv) = $store; + + if (\is_null($childQueries)) { + $childQueries = $parentQueries; + } else { + $originalQueries = $childQueries; + $childQueries = []; + + foreach ($parentQueries as $parentQuery) { + foreach ($originalQueries as $childQuery) { + $childQueries[] = array_merge( + $parentQuery, + [[Type::T_MEDIA_TYPE, [Type::T_KEYWORD, 'all']]], + $childQuery + ); + } + } + } + + return $this->multiplyMedia($env->parent, $childQueries); + } + + /** + * Convert env linked list to stack + * + * @param Environment $env + * + * @return Environment[] + * + * @phpstan-return non-empty-array + */ + protected function compactEnv(Environment $env) + { + for ($envs = []; $env; $env = $env->parent) { + $envs[] = $env; + } + + return $envs; + } + + /** + * Convert env stack to singly linked list + * + * @param Environment[] $envs + * + * @return Environment + * + * @phpstan-param non-empty-array $envs + */ + protected function extractEnv($envs) + { + for ($env = null; $e = array_pop($envs);) { + $e->parent = $env; + $env = $e; + } + + return $env; + } + + /** + * Push environment + * + * @param \ScssPhp\ScssPhp\Block $block + * + * @return \ScssPhp\ScssPhp\Compiler\Environment + */ + protected function pushEnv(Block $block = null) + { + $env = new Environment(); + $env->parent = $this->env; + $env->parentStore = $this->storeEnv; + $env->store = []; + $env->block = $block; + $env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0; + + $this->env = $env; + $this->storeEnv = null; + + return $env; + } + + /** + * Pop environment + * + * @return void + */ + protected function popEnv() + { + $this->storeEnv = $this->env->parentStore; + $this->env = $this->env->parent; + } + + /** + * Propagate vars from a just poped Env (used in @each and @for) + * + * @param array $store + * @param null|string[] $excludedVars + * + * @return void + */ + protected function backPropagateEnv($store, $excludedVars = null) + { + foreach ($store as $key => $value) { + if (empty($excludedVars) || ! \in_array($key, $excludedVars)) { + $this->set($key, $value, true); + } + } + } + + /** + * Get store environment + * + * @return \ScssPhp\ScssPhp\Compiler\Environment + */ + protected function getStoreEnv() + { + return isset($this->storeEnv) ? $this->storeEnv : $this->env; + } + + /** + * Set variable + * + * @param string $name + * @param mixed $value + * @param boolean $shadow + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param mixed $valueUnreduced + * + * @return void + */ + protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null) + { + $name = $this->normalizeName($name); + + if (! isset($env)) { + $env = $this->getStoreEnv(); + } + + if ($shadow) { + $this->setRaw($name, $value, $env, $valueUnreduced); + } else { + $this->setExisting($name, $value, $env, $valueUnreduced); + } + } + + /** + * Set existing variable + * + * @param string $name + * @param mixed $value + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param mixed $valueUnreduced + * + * @return void + */ + protected function setExisting($name, $value, Environment $env, $valueUnreduced = null) + { + $storeEnv = $env; + $specialContentKey = static::$namespaces['special'] . 'content'; + + $hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%'; + + $maxDepth = 10000; + + for (;;) { + if ($maxDepth-- <= 0) { + break; + } + + if (\array_key_exists($name, $env->store)) { + break; + } + + if (! $hasNamespace && isset($env->marker)) { + if (! empty($env->store[$specialContentKey])) { + $env = $env->store[$specialContentKey]->scope; + continue; + } + + if (! empty($env->declarationScopeParent)) { + $env = $env->declarationScopeParent; + continue; + } else { + $env = $storeEnv; + break; + } + } + + if (isset($env->parentStore)) { + $env = $env->parentStore; + } elseif (isset($env->parent)) { + $env = $env->parent; + } else { + $env = $storeEnv; + break; + } + } + + $env->store[$name] = $value; + + if ($valueUnreduced) { + $env->storeUnreduced[$name] = $valueUnreduced; + } + } + + /** + * Set raw variable + * + * @param string $name + * @param mixed $value + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param mixed $valueUnreduced + * + * @return void + */ + protected function setRaw($name, $value, Environment $env, $valueUnreduced = null) + { + $env->store[$name] = $value; + + if ($valueUnreduced) { + $env->storeUnreduced[$name] = $valueUnreduced; + } + } + + /** + * Get variable + * + * @internal + * + * @param string $name + * @param boolean $shouldThrow + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * @param boolean $unreduced + * + * @return mixed|null + */ + public function get($name, $shouldThrow = true, Environment $env = null, $unreduced = false) + { + $normalizedName = $this->normalizeName($name); + $specialContentKey = static::$namespaces['special'] . 'content'; + + if (! isset($env)) { + $env = $this->getStoreEnv(); + } + + $hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%'; + + $maxDepth = 10000; + + for (;;) { + if ($maxDepth-- <= 0) { + break; + } + + if (\array_key_exists($normalizedName, $env->store)) { + if ($unreduced && isset($env->storeUnreduced[$normalizedName])) { + return $env->storeUnreduced[$normalizedName]; + } + + return $env->store[$normalizedName]; + } + + if (! $hasNamespace && isset($env->marker)) { + if (! empty($env->store[$specialContentKey])) { + $env = $env->store[$specialContentKey]->scope; + continue; + } + + if (! empty($env->declarationScopeParent)) { + $env = $env->declarationScopeParent; + } else { + $env = $this->rootEnv; + } + continue; + } + + if (isset($env->parentStore)) { + $env = $env->parentStore; + } elseif (isset($env->parent)) { + $env = $env->parent; + } else { + break; + } + } + + if ($shouldThrow) { + throw $this->error("Undefined variable \$$name" . ($maxDepth <= 0 ? ' (infinite recursion)' : '')); + } + + // found nothing + return null; + } + + /** + * Has variable? + * + * @param string $name + * @param \ScssPhp\ScssPhp\Compiler\Environment $env + * + * @return boolean + */ + protected function has($name, Environment $env = null) + { + return ! \is_null($this->get($name, false, $env)); + } + + /** + * Inject variables + * + * @param array $args + * + * @return void + */ + protected function injectVariables(array $args) + { + if (empty($args)) { + return; + } + + $parser = $this->parserFactory(__METHOD__); + + foreach ($args as $name => $strValue) { + if ($name[0] === '$') { + $name = substr($name, 1); + } + + if (!\is_string($strValue) || ! $parser->parseValue($strValue, $value)) { + $value = $this->coerceValue($strValue); + } + + $this->set($name, $value); + } + } + + /** + * Replaces variables. + * + * @param array $variables + * + * @return void + */ + public function replaceVariables(array $variables) + { + $this->registeredVars = []; + $this->addVariables($variables); + } + + /** + * Replaces variables. + * + * @param array $variables + * + * @return void + */ + public function addVariables(array $variables) + { + $triggerWarning = false; + + foreach ($variables as $name => $value) { + if (!$value instanceof Number && !\is_array($value)) { + $triggerWarning = true; + } + + $this->registeredVars[$name] = $value; + } + + if ($triggerWarning) { + @trigger_error('Passing raw values to as custom variables to the Compiler is deprecated. Use "\ScssPhp\ScssPhp\ValueConverter::parseValue" or "\ScssPhp\ScssPhp\ValueConverter::fromPhp" to convert them instead.', E_USER_DEPRECATED); + } + } + + /** + * Set variables + * + * @api + * + * @param array $variables + * + * @return void + * + * @deprecated Use "addVariables" or "replaceVariables" instead. + */ + public function setVariables(array $variables) + { + @trigger_error('The method "setVariables" of the Compiler is deprecated. Use the "addVariables" method for the equivalent behavior or "replaceVariables" if merging with previous variables was not desired.'); + + $this->addVariables($variables); + } + + /** + * Unset variable + * + * @api + * + * @param string $name + * + * @return void + */ + public function unsetVariable($name) + { + unset($this->registeredVars[$name]); + } + + /** + * Returns list of variables + * + * @api + * + * @return array + */ + public function getVariables() + { + return $this->registeredVars; + } + + /** + * Adds to list of parsed files + * + * @internal + * + * @param string|null $path + * + * @return void + */ + public function addParsedFile($path) + { + if (! \is_null($path) && is_file($path)) { + $this->parsedFiles[realpath($path)] = filemtime($path); + } + } + + /** + * Returns list of parsed files + * + * @deprecated + * @return array + */ + public function getParsedFiles() + { + @trigger_error('The method "getParsedFiles" of the Compiler is deprecated. Use the "getIncludedFiles" method on the CompilationResult instance returned by compileString() instead. Be careful that the signature of the method is different.', E_USER_DEPRECATED); + return $this->parsedFiles; + } + + /** + * Add import path + * + * @api + * + * @param string|callable $path + * + * @return void + */ + public function addImportPath($path) + { + if (! \in_array($path, $this->importPaths)) { + $this->importPaths[] = $path; + } + } + + /** + * Set import paths + * + * @api + * + * @param string|array $path + * + * @return void + */ + public function setImportPaths($path) + { + $paths = (array) $path; + $actualImportPaths = array_filter($paths, function ($path) { + return $path !== ''; + }); + + $this->legacyCwdImportPath = \count($actualImportPaths) !== \count($paths); + + if ($this->legacyCwdImportPath) { + @trigger_error('Passing an empty string in the import paths to refer to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be used directly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.', E_USER_DEPRECATED); + } + + $this->importPaths = $actualImportPaths; + } + + /** + * Set number precision + * + * @api + * + * @param integer $numberPrecision + * + * @return void + * + * @deprecated The number precision is not configurable anymore. The default is enough for all browsers. + */ + public function setNumberPrecision($numberPrecision) + { + @trigger_error('The number precision is not configurable anymore. ' + . 'The default is enough for all browsers.', E_USER_DEPRECATED); + } + + /** + * Sets the output style. + * + * @api + * + * @param string $style One of the OutputStyle constants + * + * @return void + * + * @phpstan-param OutputStyle::* $style + */ + public function setOutputStyle($style) + { + switch ($style) { + case OutputStyle::EXPANDED: + $this->formatter = Expanded::class; + break; + + case OutputStyle::COMPRESSED: + $this->formatter = Compressed::class; + break; + + default: + throw new \InvalidArgumentException(sprintf('Invalid output style "%s".', $style)); + } + } + + /** + * Set formatter + * + * @api + * + * @param string $formatterName + * + * @return void + * + * @deprecated Use {@see setOutputStyle} instead. + */ + public function setFormatter($formatterName) + { + if (!\in_array($formatterName, [Expanded::class, Compressed::class], true)) { + @trigger_error('Formatters other than Expanded and Compressed are deprecated.', E_USER_DEPRECATED); + } + @trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED); + + $this->formatter = $formatterName; + } + + /** + * Set line number style + * + * @api + * + * @param string $lineNumberStyle + * + * @return void + * + * @deprecated The line number output is not supported anymore. Use source maps instead. + */ + public function setLineNumberStyle($lineNumberStyle) + { + @trigger_error('The line number output is not supported anymore. ' + . 'Use source maps instead.', E_USER_DEPRECATED); + } + + /** + * Configures the handling of non-ASCII outputs. + * + * If $charset is `true`, this will include a `@charset` declaration or a + * UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII + * characters. Otherwise, it will never include a `@charset` declaration or a + * byte-order mark. + * + * [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 + * + * @param bool $charset + * + * @return void + */ + public function setCharset($charset) + { + $this->charset = $charset; + } + + /** + * Enable/disable source maps + * + * @api + * + * @param integer $sourceMap + * + * @return void + * + * @phpstan-param self::SOURCE_MAP_* $sourceMap + */ + public function setSourceMap($sourceMap) + { + $this->sourceMap = $sourceMap; + } + + /** + * Set source map options + * + * @api + * + * @param array $sourceMapOptions + * + * @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $sourceMapOptions + * + * @return void + */ + public function setSourceMapOptions($sourceMapOptions) + { + $this->sourceMapOptions = $sourceMapOptions; + } + + /** + * Register function + * + * @api + * + * @param string $name + * @param callable $callback + * @param string[]|null $argumentDeclaration + * + * @return void + */ + public function registerFunction($name, $callback, $argumentDeclaration = null) + { + if (self::isNativeFunction($name)) { + @trigger_error(sprintf('The "%s" function is a core sass function. Overriding it with a custom implementation through "%s" is deprecated and won\'t be supported in ScssPhp 2.0 anymore.', $name, __METHOD__), E_USER_DEPRECATED); + } + + if ($argumentDeclaration === null) { + @trigger_error('Omitting the argument declaration when registering custom function is deprecated and won\'t be supported in ScssPhp 2.0 anymore.', E_USER_DEPRECATED); + } + + $this->userFunctions[$this->normalizeName($name)] = [$callback, $argumentDeclaration]; + } + + /** + * Unregister function + * + * @api + * + * @param string $name + * + * @return void + */ + public function unregisterFunction($name) + { + unset($this->userFunctions[$this->normalizeName($name)]); + } + + /** + * Add feature + * + * @api + * + * @param string $name + * + * @return void + * + * @deprecated Registering additional features is deprecated. + */ + public function addFeature($name) + { + @trigger_error('Registering additional features is deprecated.', E_USER_DEPRECATED); + + $this->registeredFeatures[$name] = true; + } + + /** + * Import file + * + * @param string $path + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out + * + * @return void + */ + protected function importFile($path, OutputBlock $out) + { + $this->pushCallStack('import ' . $this->getPrettyPath($path)); + // see if tree is cached + $realPath = realpath($path); + + if (substr($path, -5) === '.sass') { + $this->sourceIndex = \count($this->sourceNames); + $this->sourceNames[] = $path; + $this->sourceLine = 1; + $this->sourceColumn = 1; + + throw $this->error('The Sass indented syntax is not implemented.'); + } + + if (isset($this->importCache[$realPath])) { + $this->handleImportLoop($realPath); + + $tree = $this->importCache[$realPath]; + } else { + $code = file_get_contents($path); + $parser = $this->parserFactory($path); + $tree = $parser->parse($code); + + $this->importCache[$realPath] = $tree; + } + + $currentDirectory = $this->currentDirectory; + $this->currentDirectory = dirname($path); + + $this->compileChildrenNoReturn($tree->children, $out); + $this->currentDirectory = $currentDirectory; + $this->popCallStack(); + } + + /** + * Save the imported files with their resolving path context + * + * @param string|null $currentDirectory + * @param string $path + * @param string $filePath + * + * @return void + */ + private function registerImport($currentDirectory, $path, $filePath) + { + $this->resolvedImports[] = ['currentDir' => $currentDirectory, 'path' => $path, 'filePath' => $filePath]; + } + + /** + * Detects whether the import is a CSS import. + * + * For legacy reasons, custom importers are called for those, allowing them + * to replace them with an actual Sass import. However this behavior is + * deprecated. Custom importers are expected to return null when they receive + * a CSS import. + * + * @param string $url + * + * @return bool + */ + public static function isCssImport($url) + { + return 1 === preg_match('~\.css$|^https?://|^//~', $url); + } + + /** + * Return the file path for an import url if it exists + * + * @internal + * + * @param string $url + * @param string|null $currentDir + * + * @return string|null + */ + public function findImport($url, $currentDir = null) + { + // Vanilla css and external requests. These are not meant to be Sass imports. + // Callback importers are still called for BC. + if (self::isCssImport($url)) { + foreach ($this->importPaths as $dir) { + if (\is_string($dir)) { + continue; + } + + if (\is_callable($dir)) { + // check custom callback for import path + $file = \call_user_func($dir, $url); + + if (! \is_null($file)) { + if (\is_array($dir)) { + $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1]; + } elseif ($dir instanceof \Closure) { + $r = new \ReflectionFunction($dir); + if (false !== strpos($r->name, '{closure}')) { + $callableDescription = sprintf('closure{%s:%s}', $r->getFileName(), $r->getStartLine()); + } elseif ($class = $r->getClosureScopeClass()) { + $callableDescription = $class->name.'::'.$r->name; + } else { + $callableDescription = $r->name; + } + } elseif (\is_object($dir)) { + $callableDescription = \get_class($dir) . '::__invoke'; + } else { + $callableDescription = 'callable'; // Fallback if we don't have a dedicated description + } + @trigger_error(sprintf('Returning a file to import for CSS or external references in custom importer callables is deprecated and will not be supported anymore in ScssPhp 2.0. This behavior is not compliant with the Sass specification. Update your "%s" importer.', $callableDescription), E_USER_DEPRECATED); + + return $file; + } + } + } + return null; + } + + if (!\is_null($currentDir)) { + $relativePath = $this->resolveImportPath($url, $currentDir); + + if (!\is_null($relativePath)) { + return $relativePath; + } + } + + foreach ($this->importPaths as $dir) { + if (\is_string($dir)) { + $path = $this->resolveImportPath($url, $dir); + + if (!\is_null($path)) { + return $path; + } + } elseif (\is_callable($dir)) { + // check custom callback for import path + $file = \call_user_func($dir, $url); + + if (! \is_null($file)) { + return $file; + } + } + } + + if ($this->legacyCwdImportPath) { + $path = $this->resolveImportPath($url, getcwd()); + + if (!\is_null($path)) { + @trigger_error('Resolving imports relatively to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be added as an import path explicitly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.', E_USER_DEPRECATED); + + return $path; + } + } + + throw $this->error("`$url` file not found for @import"); + } + + /** + * @param string $url + * @param string $baseDir + * + * @return string|null + */ + private function resolveImportPath($url, $baseDir) + { + $path = Path::join($baseDir, $url); + + $hasExtension = preg_match('/.s[ac]ss$/', $url); + + if ($hasExtension) { + return $this->checkImportPathConflicts($this->tryImportPath($path)); + } + + $result = $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path)); + + if (!\is_null($result)) { + return $result; + } + + return $this->tryImportPathAsDirectory($path); + } + + /** + * @param string[] $paths + * + * @return string|null + */ + private function checkImportPathConflicts(array $paths) + { + if (\count($paths) === 0) { + return null; + } + + if (\count($paths) === 1) { + return $paths[0]; + } + + $formattedPrettyPaths = []; + + foreach ($paths as $path) { + $formattedPrettyPaths[] = ' ' . $this->getPrettyPath($path); + } + + throw $this->error("It's not clear which file to import. Found:\n" . implode("\n", $formattedPrettyPaths)); + } + + /** + * @param string $path + * + * @return string[] + */ + private function tryImportPathWithExtensions($path) + { + $result = array_merge( + $this->tryImportPath($path.'.sass'), + $this->tryImportPath($path.'.scss') + ); + + if ($result) { + return $result; + } + + return $this->tryImportPath($path.'.css'); + } + + /** + * @param string $path + * + * @return string[] + */ + private function tryImportPath($path) + { + $partial = dirname($path).'/_'.basename($path); + + $candidates = []; + + if (is_file($partial)) { + $candidates[] = $partial; + } + + if (is_file($path)) { + $candidates[] = $path; + } + + return $candidates; + } + + /** + * @param string $path + * + * @return string|null + */ + private function tryImportPathAsDirectory($path) + { + if (!is_dir($path)) { + return null; + } + + return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index')); + } + + /** + * @param string|null $path + * + * @return string + */ + private function getPrettyPath($path) + { + if ($path === null) { + return '(unknown file)'; + } + + $normalizedPath = $path; + $normalizedRootDirectory = $this->rootDirectory.'/'; + + if (\DIRECTORY_SEPARATOR === '\\') { + $normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory); + $normalizedPath = str_replace('\\', '/', $path); + } + + if (0 === strpos($normalizedPath, $normalizedRootDirectory)) { + return substr($path, \strlen($normalizedRootDirectory)); + } + + return $path; + } + + /** + * Set encoding + * + * @api + * + * @param string|null $encoding + * + * @return void + * + * @deprecated Non-compliant support for other encodings than UTF-8 is deprecated. + */ + public function setEncoding($encoding) + { + if (!$encoding || strtolower($encoding) === 'utf-8') { + @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED); + } else { + @trigger_error(sprintf('The "%s" method is deprecated. Parsing will only support UTF-8 in ScssPhp 2.0. The non-UTF-8 parsing of ScssPhp 1.x is not spec compliant.', __METHOD__), E_USER_DEPRECATED); + } + + $this->encoding = $encoding; + } + + /** + * Ignore errors? + * + * @api + * + * @param boolean $ignoreErrors + * + * @return \ScssPhp\ScssPhp\Compiler + * + * @deprecated Ignoring Sass errors is not longer supported. + */ + public function setIgnoreErrors($ignoreErrors) + { + @trigger_error('Ignoring Sass errors is not longer supported.', E_USER_DEPRECATED); + + return $this; + } + + /** + * Get source position + * + * @api + * + * @return array + * + * @deprecated + */ + public function getSourcePosition() + { + @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED); + + $sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : ''; + + return [$sourceFile, $this->sourceLine, $this->sourceColumn]; + } + + /** + * Throw error (exception) + * + * @api + * + * @param string $msg Message with optional sprintf()-style vararg parameters + * + * @throws \ScssPhp\ScssPhp\Exception\CompilerException + * + * @deprecated use "error" and throw the exception in the caller instead. + */ + public function throwError($msg) + { + @trigger_error( + 'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead', + E_USER_DEPRECATED + ); + + throw $this->error(...func_get_args()); + } + + /** + * Build an error (exception) + * + * @internal + * + * @param string $msg Message with optional sprintf()-style vararg parameters + * + * @return CompilerException + */ + public function error($msg, ...$args) + { + if ($args) { + $msg = sprintf($msg, ...$args); + } + + if (! $this->ignoreCallStackMessage) { + $msg = $this->addLocationToMessage($msg); + } + + return new CompilerException($msg); + } + + /** + * @param string $msg + * + * @return string + */ + private function addLocationToMessage($msg) + { + $line = $this->sourceLine; + $column = $this->sourceColumn; + + $loc = isset($this->sourceNames[$this->sourceIndex]) + ? $this->getPrettyPath($this->sourceNames[$this->sourceIndex]) . " on line $line, at column $column" + : "line: $line, column: $column"; + + $msg = "$msg: $loc"; + + $callStackMsg = $this->callStackMessage(); + + if ($callStackMsg) { + $msg .= "\nCall Stack:\n" . $callStackMsg; + } + + return $msg; + } + + /** + * @param string $functionName + * @param array $ExpectedArgs + * @param int $nbActual + * @return CompilerException + * + * @deprecated + */ + public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual) + { + @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED); + + $nbExpected = \count($ExpectedArgs); + + if ($nbActual > $nbExpected) { + return $this->error( + 'Error: Only %d arguments allowed in %s(), but %d were passed.', + $nbExpected, + $functionName, + $nbActual + ); + } else { + $missing = []; + + while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) { + array_unshift($missing, array_pop($ExpectedArgs)); + } + + return $this->error( + 'Error: %s() argument%s %s missing.', + $functionName, + count($missing) > 1 ? 's' : '', + implode(', ', $missing) + ); + } + } + + /** + * Beautify call stack for output + * + * @param boolean $all + * @param int|null $limit + * + * @return string + */ + protected function callStackMessage($all = false, $limit = null) + { + $callStackMsg = []; + $ncall = 0; + + if ($this->callStack) { + foreach (array_reverse($this->callStack) as $call) { + if ($all || (isset($call['n']) && $call['n'])) { + $msg = '#' . $ncall++ . ' ' . $call['n'] . ' '; + $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) + ? $this->getPrettyPath($this->sourceNames[$call[Parser::SOURCE_INDEX]]) + : '(unknown file)'); + $msg .= ' on line ' . $call[Parser::SOURCE_LINE]; + + $callStackMsg[] = $msg; + + if (! \is_null($limit) && $ncall > $limit) { + break; + } + } + } + } + + return implode("\n", $callStackMsg); + } + + /** + * Handle import loop + * + * @param string $name + * + * @throws \Exception + */ + protected function handleImportLoop($name) + { + for ($env = $this->env; $env; $env = $env->parent) { + if (! $env->block) { + continue; + } + + $file = $this->sourceNames[$env->block->sourceIndex]; + + if ($file === null) { + continue; + } + + if (realpath($file) === $name) { + throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file)); + } + } + } + + /** + * Call SCSS @function + * + * @param Object $func + * @param array $argValues + * + * @return array|Number + */ + protected function callScssFunction($func, $argValues) + { + if (! $func) { + return static::$defaultValue; + } + $name = $func->name; + + $this->pushEnv(); + + // set the args + if (isset($func->args)) { + $this->applyArguments($func->args, $argValues); + } + + // throw away lines and children + $tmp = new OutputBlock(); + $tmp->lines = []; + $tmp->children = []; + + $this->env->marker = 'function'; + + if (! empty($func->parentEnv)) { + $this->env->declarationScopeParent = $func->parentEnv; + } else { + throw $this->error("@function $name() without parentEnv"); + } + + $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . ' ' . $name); + + $this->popEnv(); + + return ! isset($ret) ? static::$defaultValue : $ret; + } + + /** + * Call built-in and registered (PHP) functions + * + * @param string $name + * @param callable $function + * @param array $prototype + * @param array $args + * + * @return array|Number|null + */ + protected function callNativeFunction($name, $function, $prototype, $args) + { + $libName = (is_array($function) ? end($function) : null); + $sorted_kwargs = $this->sortNativeFunctionArgs($libName, $prototype, $args); + + if (\is_null($sorted_kwargs)) { + return null; + } + @list($sorted, $kwargs) = $sorted_kwargs; + + if ($name !== 'if') { + foreach ($sorted as &$val) { + if ($val !== null) { + $val = $this->reduce($val, true); + } + } + } + + $returnValue = \call_user_func($function, $sorted, $kwargs); + + if (! isset($returnValue)) { + return null; + } + + if (\is_array($returnValue) || $returnValue instanceof Number) { + return $returnValue; + } + + @trigger_error(sprintf('Returning a PHP value from the "%s" custom function is deprecated. A sass value must be returned instead.', $name), E_USER_DEPRECATED); + + return $this->coerceValue($returnValue); + } + + /** + * Get built-in function + * + * @param string $name Normalized name + * + * @return array + */ + protected function getBuiltinFunction($name) + { + $libName = self::normalizeNativeFunctionName($name); + return [$this, $libName]; + } + + /** + * Normalize native function name + * + * @internal + * + * @param string $name + * + * @return string + */ + public static function normalizeNativeFunctionName($name) + { + $name = str_replace("-", "_", $name); + $libName = 'lib' . preg_replace_callback( + '/_(.)/', + function ($m) { + return ucfirst($m[1]); + }, + ucfirst($name) + ); + return $libName; + } + + /** + * Check if a function is a native built-in scss function, for css parsing + * + * @internal + * + * @param string $name + * + * @return bool + */ + public static function isNativeFunction($name) + { + return method_exists(Compiler::class, self::normalizeNativeFunctionName($name)); + } + + /** + * Sorts keyword arguments + * + * @param string $functionName + * @param array|null $prototypes + * @param array $args + * + * @return array|null + */ + protected function sortNativeFunctionArgs($functionName, $prototypes, $args) + { + static $parser = null; + + if (! isset($prototypes)) { + $keyArgs = []; + $posArgs = []; + + if (\is_array($args) && \count($args) && \end($args) === static::$null) { + array_pop($args); + } + + // separate positional and keyword arguments + foreach ($args as $arg) { + list($key, $value) = $arg; + + if (empty($key) or empty($key[1])) { + $posArgs[] = empty($arg[2]) ? $value : $arg; + } else { + $keyArgs[$key[1]] = $value; + } + } + + return [$posArgs, $keyArgs]; + } + + // specific cases ? + if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) { + // notation 100 127 255 / 0 is in fact a simple list of 4 values + foreach ($args as $k => $arg) { + if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) { + $args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]); + } + } + } + + list($positionalArgs, $namedArgs, $names, $separator, $hasSplat) = $this->evaluateArguments($args, false); + + if (! \is_array(reset($prototypes))) { + $prototypes = [$prototypes]; + } + + $parsedPrototypes = array_map([$this, 'parseFunctionPrototype'], $prototypes); + assert(!empty($parsedPrototypes)); + $matchedPrototype = $this->selectFunctionPrototype($parsedPrototypes, \count($positionalArgs), $names); + + $this->verifyPrototype($matchedPrototype, \count($positionalArgs), $names, $hasSplat); + + $vars = $this->applyArgumentsToDeclaration($matchedPrototype, $positionalArgs, $namedArgs, $separator); + + $finalArgs = []; + $keyArgs = []; + + foreach ($matchedPrototype['arguments'] as $argument) { + list($normalizedName, $originalName, $default) = $argument; + + if (isset($vars[$normalizedName])) { + $value = $vars[$normalizedName]; + } else { + $value = $default; + } + + // special null value as default: translate to real null here + if ($value === [Type::T_KEYWORD, 'null']) { + $value = null; + } + + $finalArgs[] = $value; + $keyArgs[$originalName] = $value; + } + + if ($matchedPrototype['rest_argument'] !== null) { + $value = $vars[$matchedPrototype['rest_argument']]; + + $finalArgs[] = $value; + $keyArgs[$matchedPrototype['rest_argument']] = $value; + } + + return [$finalArgs, $keyArgs]; + } + + /** + * Parses a function prototype to the internal representation of arguments. + * + * The input is an array of strings describing each argument, as supported + * in {@see registerFunction}. Argument names don't include the `$`. + * The output contains the list of positional argument, with their normalized + * name (underscores are replaced by dashes), their original name (to be used + * in case of error reporting) and their default value. The output also contains + * the normalized name of the rest argument, or null if the function prototype + * is not variadic. + * + * @param string[] $prototype + * + * @return array + * @phpstan-return array{arguments: list, rest_argument: string|null} + */ + private function parseFunctionPrototype(array $prototype) + { + static $parser = null; + + $arguments = []; + $restArgument = null; + + foreach ($prototype as $p) { + if (null !== $restArgument) { + throw new \InvalidArgumentException('The argument declaration is invalid. The rest argument must be the last one.'); + } + + $default = null; + $p = explode(':', $p, 2); + $name = str_replace('_', '-', $p[0]); + + if (isset($p[1])) { + $defaultSource = trim($p[1]); + + if ($defaultSource === 'null') { + // differentiate this null from the static::$null + $default = [Type::T_KEYWORD, 'null']; + } else { + if (\is_null($parser)) { + $parser = $this->parserFactory(__METHOD__); + } + + $parser->parseValue($defaultSource, $default); + } + } + + if (substr($name, -3) === '...') { + $restArgument = substr($name, 0, -3); + } else { + $arguments[] = [$name, $p[0], $default]; + } + } + + return [ + 'arguments' => $arguments, + 'rest_argument' => $restArgument, + ]; + } + + /** + * Returns the function prototype for the given positional and named arguments. + * + * If no exact match is found, finds the closest approximation. Note that this + * doesn't guarantee that $positional and $names are valid for the returned + * prototype. + * + * @param array[] $prototypes + * @param int $positional + * @param array $names A set of names, as both keys and values + * + * @return array + * + * @phpstan-param non-empty-list, rest_argument: string|null}> $prototypes + * @phpstan-return array{arguments: list, rest_argument: string|null} + */ + private function selectFunctionPrototype(array $prototypes, $positional, array $names) + { + $fuzzyMatch = null; + $minMismatchDistance = null; + + foreach ($prototypes as $prototype) { + // Ideally, find an exact match. + if ($this->checkPrototypeMatches($prototype, $positional, $names)) { + return $prototype; + } + + $mismatchDistance = \count($prototype['arguments']) - $positional; + + if ($minMismatchDistance !== null) { + if (abs($mismatchDistance) > abs($minMismatchDistance)) { + continue; + } + + // If two overloads have the same mismatch distance, favor the overload + // that has more arguments. + if (abs($mismatchDistance) === abs($minMismatchDistance) && $mismatchDistance < 0) { + continue; + } + } + + $minMismatchDistance = $mismatchDistance; + $fuzzyMatch = $prototype; + } + + return $fuzzyMatch; + } + + /** + * Checks whether the argument invocation matches the callable prototype. + * + * The rules are similar to {@see verifyPrototype}. The boolean return value + * avoids the overhead of building and catching exceptions when the reason of + * not matching the prototype does not need to be known. + * + * @param array $prototype + * @param int $positional + * @param array $names + * + * @return bool + * + * @phpstan-param array{arguments: list, rest_argument: string|null} $prototype + */ + private function checkPrototypeMatches(array $prototype, $positional, array $names) + { + $nameUsed = 0; + + foreach ($prototype['arguments'] as $i => $argument) { + list ($name, $originalName, $default) = $argument; + + if ($i < $positional) { + if (isset($names[$name])) { + return false; + } + } elseif (isset($names[$name])) { + $nameUsed++; + } elseif ($default === null) { + return false; + } + } + + if ($prototype['rest_argument'] !== null) { + return true; + } + + if ($positional > \count($prototype['arguments'])) { + return false; + } + + if ($nameUsed < \count($names)) { + return false; + } + + return true; + } + + /** + * Verifies that the argument invocation is valid for the callable prototype. + * + * @param array $prototype + * @param int $positional + * @param array $names + * @param bool $hasSplat + * + * @return void + * + * @throws SassScriptException + * + * @phpstan-param array{arguments: list, rest_argument: string|null} $prototype + */ + private function verifyPrototype(array $prototype, $positional, array $names, $hasSplat) + { + $nameUsed = 0; + + foreach ($prototype['arguments'] as $i => $argument) { + list ($name, $originalName, $default) = $argument; + + if ($i < $positional) { + if (isset($names[$name])) { + throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName)); + } + } elseif (isset($names[$name])) { + $nameUsed++; + } elseif ($default === null) { + throw new SassScriptException(sprintf('Missing argument $%s', $originalName)); + } + } + + if ($prototype['rest_argument'] !== null) { + return; + } + + if ($positional > \count($prototype['arguments'])) { + $message = sprintf( + 'Only %d %sargument%s allowed, but %d %s passed.', + \count($prototype['arguments']), + empty($names) ? '' : 'positional ', + \count($prototype['arguments']) === 1 ? '' : 's', + $positional, + $positional === 1 ? 'was' : 'were' + ); + if (!$hasSplat) { + throw new SassScriptException($message); + } + + $message = $this->addLocationToMessage($message); + $message .= "\nThis will be an error in future versions of Sass."; + $this->logger->warn($message, true); + } + + if ($nameUsed < \count($names)) { + $unknownNames = array_values(array_diff($names, array_column($prototype['arguments'], 0))); + $lastName = array_pop($unknownNames); + $message = sprintf( + 'No argument%s named $%s%s.', + $unknownNames ? 's' : '', + $unknownNames ? implode(', $', $unknownNames) . ' or $' : '', + $lastName + ); + throw new SassScriptException($message); + } + } + + /** + * Evaluates the argument from the invocation. + * + * This returns several things about this invocation: + * - the list of positional arguments + * - the map of named arguments, indexed by normalized names + * - the set of names used in the arguments (that's an array using the normalized names as keys for O(1) access) + * - the separator used by the list using the splat operator, if any + * - a boolean indicator whether any splat argument (list or map) was used, to support the incomplete error reporting. + * + * @param array[] $args + * @param bool $reduce Whether arguments should be reduced to their value + * + * @return array + * + * @throws SassScriptException + * + * @phpstan-return array{0: list, 1: array, 2: array, 3: string|null, 4: bool} + */ + private function evaluateArguments(array $args, $reduce = true) + { + // this represents trailing commas + if (\count($args) && end($args) === static::$null) { + array_pop($args); + } + + $splatSeparator = null; + $keywordArgs = []; + $names = []; + $positionalArgs = []; + $hasKeywordArgument = false; + $hasSplat = false; + + foreach ($args as $arg) { + if (!empty($arg[0])) { + $hasKeywordArgument = true; + + assert(\is_string($arg[0][1])); + $name = str_replace('_', '-', $arg[0][1]); + + if (isset($keywordArgs[$name])) { + throw new SassScriptException(sprintf('Duplicate named argument $%s.', $arg[0][1])); + } + + $keywordArgs[$name] = $this->maybeReduce($reduce, $arg[1]); + $names[$name] = $name; + } elseif (! empty($arg[2])) { + // $arg[2] means a var followed by ... in the arg ($list... ) + $val = $this->reduce($arg[1], true); + $hasSplat = true; + + if ($val[0] === Type::T_LIST) { + foreach ($val[2] as $item) { + if (\is_null($splatSeparator)) { + $splatSeparator = $val[1]; + } + + $positionalArgs[] = $this->maybeReduce($reduce, $item); + } + + if (isset($val[3]) && \is_array($val[3])) { + foreach ($val[3] as $name => $item) { + assert(\is_string($name)); + + $normalizedName = str_replace('_', '-', $name); + + if (isset($keywordArgs[$normalizedName])) { + throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name)); + } + + $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item); + $names[$normalizedName] = $normalizedName; + $hasKeywordArgument = true; + } + } + } elseif ($val[0] === Type::T_MAP) { + foreach ($val[1] as $i => $name) { + $name = $this->compileStringContent($this->coerceString($name)); + $item = $val[2][$i]; + + if (! is_numeric($name)) { + $normalizedName = str_replace('_', '-', $name); + + if (isset($keywordArgs[$normalizedName])) { + throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name)); + } + + $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item); + $names[$normalizedName] = $normalizedName; + $hasKeywordArgument = true; + } else { + if (\is_null($splatSeparator)) { + $splatSeparator = $val[1]; + } + + $positionalArgs[] = $this->maybeReduce($reduce, $item); + } + } + } elseif ($val[0] !== Type::T_NULL) { // values other than null are treated a single-element lists, while null is the empty list + $positionalArgs[] = $this->maybeReduce($reduce, $val); + } + } elseif ($hasKeywordArgument) { + throw new SassScriptException('Positional arguments must come before keyword arguments.'); + } else { + $positionalArgs[] = $this->maybeReduce($reduce, $arg[1]); + } + } + + return [$positionalArgs, $keywordArgs, $names, $splatSeparator, $hasSplat]; + } + + /** + * @param bool $reduce + * @param array|Number $value + * + * @return array|Number + */ + private function maybeReduce($reduce, $value) + { + if ($reduce) { + return $this->reduce($value, true); + } + + return $value; + } + + /** + * Apply argument values per definition + * + * @param array[] $argDef + * @param array|null $argValues + * @param boolean $storeInEnv + * @param boolean $reduce + * only used if $storeInEnv = false + * + * @return array + * + * @phpstan-param list $argDef + * + * @throws \Exception + */ + protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true) + { + $output = []; + + if (\is_null($argValues)) { + $argValues = []; + } + + if ($storeInEnv) { + $storeEnv = $this->getStoreEnv(); + + $env = new Environment(); + $env->store = $storeEnv->store; + } + + $prototype = ['arguments' => [], 'rest_argument' => null]; + $originalRestArgumentName = null; + + foreach ($argDef as $i => $arg) { + list($name, $default, $isVariable) = $arg; + $normalizedName = str_replace('_', '-', $name); + + if ($isVariable) { + $originalRestArgumentName = $name; + $prototype['rest_argument'] = $normalizedName; + } else { + $prototype['arguments'][] = [$normalizedName, $name, !empty($default) ? $default : null]; + } + } + + list($positionalArgs, $namedArgs, $names, $splatSeparator, $hasSplat) = $this->evaluateArguments($argValues, $reduce); + + $this->verifyPrototype($prototype, \count($positionalArgs), $names, $hasSplat); + + $vars = $this->applyArgumentsToDeclaration($prototype, $positionalArgs, $namedArgs, $splatSeparator); + + foreach ($prototype['arguments'] as $argument) { + list($normalizedName, $name) = $argument; + + if (!isset($vars[$normalizedName])) { + continue; + } + + $val = $vars[$normalizedName]; + + if ($storeInEnv) { + $this->set($name, $this->reduce($val, true), true, $env); + } else { + $output[$name] = ($reduce ? $this->reduce($val, true) : $val); + } + } + + if ($prototype['rest_argument'] !== null) { + assert($originalRestArgumentName !== null); + $name = $originalRestArgumentName; + $val = $vars[$prototype['rest_argument']]; + + if ($storeInEnv) { + $this->set($name, $this->reduce($val, true), true, $env); + } else { + $output[$name] = ($reduce ? $this->reduce($val, true) : $val); + } + } + + if ($storeInEnv) { + $storeEnv->store = $env->store; + } + + foreach ($prototype['arguments'] as $argument) { + list($normalizedName, $name, $default) = $argument; + + if (isset($vars[$normalizedName])) { + continue; + } + assert($default !== null); + + if ($storeInEnv) { + $this->set($name, $this->reduce($default, true), true); + } else { + $output[$name] = ($reduce ? $this->reduce($default, true) : $default); + } + } + + return $output; + } + + /** + * Apply argument values per definition. + * + * This method assumes that the arguments are valid for the provided prototype. + * The validation with {@see verifyPrototype} must have been run before calling + * it. + * Arguments are returned as a map from the normalized argument names to the + * value. Additional arguments are collected in a sass argument list available + * under the name of the rest argument in the result. + * + * Defaults are not applied as they are resolved in a different environment. + * + * @param array $prototype + * @param array $positionalArgs + * @param array $namedArgs + * @param string|null $splatSeparator + * + * @return array + * + * @phpstan-param array{arguments: list, rest_argument: string|null} $prototype + */ + private function applyArgumentsToDeclaration(array $prototype, array $positionalArgs, array $namedArgs, $splatSeparator) + { + $output = []; + $minLength = min(\count($positionalArgs), \count($prototype['arguments'])); + + for ($i = 0; $i < $minLength; $i++) { + list($name) = $prototype['arguments'][$i]; + $val = $positionalArgs[$i]; + + $output[$name] = $val; + } + + $restNamed = $namedArgs; + + for ($i = \count($positionalArgs); $i < \count($prototype['arguments']); $i++) { + $argument = $prototype['arguments'][$i]; + list($name) = $argument; + + if (isset($namedArgs[$name])) { + $val = $namedArgs[$name]; + unset($restNamed[$name]); + } else { + continue; + } + + $output[$name] = $val; + } + + if ($prototype['rest_argument'] !== null) { + $name = $prototype['rest_argument']; + $rest = array_values(array_slice($positionalArgs, \count($prototype['arguments']))); + + $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , $rest, $restNamed]; + + $output[$name] = $val; + } + + return $output; + } + + /** + * Coerce a php value into a scss one + * + * @param mixed $value + * + * @return array|Number + */ + protected function coerceValue($value) + { + if (\is_array($value) || $value instanceof Number) { + return $value; + } + + if (\is_bool($value)) { + return $this->toBool($value); + } + + if (\is_null($value)) { + return static::$null; + } + + if (is_numeric($value)) { + return new Number($value, ''); + } + + if ($value === '') { + return static::$emptyString; + } + + $value = [Type::T_KEYWORD, $value]; + $color = $this->coerceColor($value); + + if ($color) { + return $color; + } + + return $value; + } + + /** + * Coerce something to map + * + * @param array|Number $item + * + * @return array|Number + */ + protected function coerceMap($item) + { + if ($item[0] === Type::T_MAP) { + return $item; + } + + if ( + $item[0] === Type::T_LIST && + $item[2] === [] + ) { + return static::$emptyMap; + } + + return $item; + } + + /** + * Coerce something to list + * + * @param array|Number $item + * @param string $delim + * @param boolean $removeTrailingNull + * + * @return array + */ + protected function coerceList($item, $delim = ',', $removeTrailingNull = false) + { + if ($item instanceof Number) { + return [Type::T_LIST, $delim, [$item]]; + } + + if ($item[0] === Type::T_LIST) { + // remove trailing null from the list + if ($removeTrailingNull && end($item[2]) === static::$null) { + array_pop($item[2]); + } + + return $item; + } + + if ($item[0] === Type::T_MAP) { + $keys = $item[1]; + $values = $item[2]; + $list = []; + + for ($i = 0, $s = \count($keys); $i < $s; $i++) { + $key = $keys[$i]; + $value = $values[$i]; + + switch ($key[0]) { + case Type::T_LIST: + case Type::T_MAP: + case Type::T_STRING: + case Type::T_NULL: + break; + + default: + $key = [Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))]; + break; + } + + $list[] = [ + Type::T_LIST, + '', + [$key, $value] + ]; + } + + return [Type::T_LIST, ',', $list]; + } + + return [Type::T_LIST, $delim, [$item]]; + } + + /** + * Coerce color for expression + * + * @param array|Number $value + * + * @return array|Number + */ + protected function coerceForExpression($value) + { + if ($color = $this->coerceColor($value)) { + return $color; + } + + return $value; + } + + /** + * Coerce value to color + * + * @param array|Number $value + * @param bool $inRGBFunction + * + * @return array|null + */ + protected function coerceColor($value, $inRGBFunction = false) + { + if ($value instanceof Number) { + return null; + } + + switch ($value[0]) { + case Type::T_COLOR: + for ($i = 1; $i <= 3; $i++) { + if (! is_numeric($value[$i])) { + $cv = $this->compileRGBAValue($value[$i]); + + if (! is_numeric($cv)) { + return null; + } + + $value[$i] = $cv; + } + + if (isset($value[4])) { + if (! is_numeric($value[4])) { + $cv = $this->compileRGBAValue($value[4], true); + + if (! is_numeric($cv)) { + return null; + } + + $value[4] = $cv; + } + } + } + + return $value; + + case Type::T_LIST: + if ($inRGBFunction) { + if (\count($value[2]) == 3 || \count($value[2]) == 4) { + $color = $value[2]; + array_unshift($color, Type::T_COLOR); + + return $this->coerceColor($color); + } + } + + return null; + + case Type::T_KEYWORD: + if (! \is_string($value[1])) { + return null; + } + + $name = strtolower($value[1]); + + // hexa color? + if (preg_match('/^#([0-9a-f]+)$/i', $name, $m)) { + $nofValues = \strlen($m[1]); + + if (\in_array($nofValues, [3, 4, 6, 8])) { + $nbChannels = 3; + $color = []; + $num = hexdec($m[1]); + + switch ($nofValues) { + case 4: + $nbChannels = 4; + // then continuing with the case 3: + case 3: + for ($i = 0; $i < $nbChannels; $i++) { + $t = $num & 0xf; + array_unshift($color, $t << 4 | $t); + $num >>= 4; + } + + break; + + case 8: + $nbChannels = 4; + // then continuing with the case 6: + case 6: + for ($i = 0; $i < $nbChannels; $i++) { + array_unshift($color, $num & 0xff); + $num >>= 8; + } + + break; + } + + if ($nbChannels === 4) { + if ($color[3] === 255) { + $color[3] = 1; // fully opaque + } else { + $color[3] = round($color[3] / 255, Number::PRECISION); + } + } + + array_unshift($color, Type::T_COLOR); + + return $color; + } + } + + if ($rgba = Colors::colorNameToRGBa($name)) { + return isset($rgba[3]) + ? [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2], $rgba[3]] + : [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2]]; + } + + return null; + } + + return null; + } + + /** + * @param integer|Number $value + * @param boolean $isAlpha + * + * @return integer|mixed + */ + protected function compileRGBAValue($value, $isAlpha = false) + { + if ($isAlpha) { + return $this->compileColorPartValue($value, 0, 1, false); + } + + return $this->compileColorPartValue($value, 0, 255, true); + } + + /** + * @param mixed $value + * @param integer|float $min + * @param integer|float $max + * @param boolean $isInt + * + * @return integer|mixed + */ + protected function compileColorPartValue($value, $min, $max, $isInt = true) + { + if (! is_numeric($value)) { + if (\is_array($value)) { + $reduced = $this->reduce($value); + + if ($reduced instanceof Number) { + $value = $reduced; + } + } + + if ($value instanceof Number) { + if ($value->unitless()) { + $num = $value->getDimension(); + } elseif ($value->hasUnit('%')) { + $num = $max * $value->getDimension() / 100; + } else { + throw $this->error('Expected %s to have no units or "%%".', $value); + } + + $value = $num; + } elseif (\is_array($value)) { + $value = $this->compileValue($value); + } + } + + if (is_numeric($value)) { + if ($isInt) { + $value = round($value); + } + + $value = min($max, max($min, $value)); + + return $value; + } + + return $value; + } + + /** + * Coerce value to string + * + * @param array|Number $value + * + * @return array + */ + protected function coerceString($value) + { + if ($value[0] === Type::T_STRING) { + return $value; + } + + return [Type::T_STRING, '', [$this->compileValue($value)]]; + } + + /** + * Assert value is a string + * + * This method deals with internal implementation details of the value + * representation where unquoted strings can sometimes be stored under + * other types. + * The returned value is always using the T_STRING type. + * + * @api + * + * @param array|Number $value + * @param string|null $varName + * + * @return array + * + * @throws SassScriptException + */ + public function assertString($value, $varName = null) + { + // case of url(...) parsed a a function + if ($value[0] === Type::T_FUNCTION) { + $value = $this->coerceString($value); + } + + if (! \in_array($value[0], [Type::T_STRING, Type::T_KEYWORD])) { + $value = $this->compileValue($value); + throw SassScriptException::forArgument("$value is not a string.", $varName); + } + + return $this->coerceString($value); + } + + /** + * Coerce value to a percentage + * + * @param array|Number $value + * + * @return integer|float + * + * @deprecated + */ + protected function coercePercent($value) + { + @trigger_error(sprintf('"%s" is deprecated since 1.7.0.', __METHOD__), E_USER_DEPRECATED); + + if ($value instanceof Number) { + if ($value->hasUnit('%')) { + return $value->getDimension() / 100; + } + + return $value->getDimension(); + } + + return 0; + } + + /** + * Assert value is a map + * + * @api + * + * @param array|Number $value + * @param string|null $varName + * + * @return array + * + * @throws SassScriptException + */ + public function assertMap($value, $varName = null) + { + $value = $this->coerceMap($value); + + if ($value[0] !== Type::T_MAP) { + $value = $this->compileValue($value); + + throw SassScriptException::forArgument("$value is not a map.", $varName); + } + + return $value; + } + + /** + * Assert value is a list + * + * @api + * + * @param array|Number $value + * + * @return array + * + * @throws \Exception + */ + public function assertList($value) + { + if ($value[0] !== Type::T_LIST) { + throw $this->error('expecting list, %s received', $value[0]); + } + + return $value; + } + + /** + * Gets the keywords of an argument list. + * + * Keys in the returned array are normalized names (underscores are replaced with dashes) + * without the leading `$`. + * Calling this helper with anything that an argument list received for a rest argument + * of the function argument declaration is not supported. + * + * @param array|Number $value + * + * @return array + */ + public function getArgumentListKeywords($value) + { + if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) { + throw new \InvalidArgumentException('The argument is not a sass argument list.'); + } + + return $value[3]; + } + + /** + * Assert value is a color + * + * @api + * + * @param array|Number $value + * @param string|null $varName + * + * @return array + * + * @throws SassScriptException + */ + public function assertColor($value, $varName = null) + { + if ($color = $this->coerceColor($value)) { + return $color; + } + + $value = $this->compileValue($value); + + throw SassScriptException::forArgument("$value is not a color.", $varName); + } + + /** + * Assert value is a number + * + * @api + * + * @param array|Number $value + * @param string|null $varName + * + * @return Number + * + * @throws SassScriptException + */ + public function assertNumber($value, $varName = null) + { + if (!$value instanceof Number) { + $value = $this->compileValue($value); + throw SassScriptException::forArgument("$value is not a number.", $varName); + } + + return $value; + } + + /** + * Assert value is a integer + * + * @api + * + * @param array|Number $value + * @param string|null $varName + * + * @return integer + * + * @throws SassScriptException + */ + public function assertInteger($value, $varName = null) + { + $value = $this->assertNumber($value, $varName)->getDimension(); + if (round($value - \intval($value), Number::PRECISION) > 0) { + throw SassScriptException::forArgument("$value is not an integer.", $varName); + } + + return intval($value); + } + + /** + * Extract the ... / alpha on the last argument of channel arg + * in color functions + * + * @param array $args + * @return array + */ + private function extractSlashAlphaInColorFunction($args) + { + $last = end($args); + if (\count($args) === 3 && $last[0] === Type::T_EXPRESSION && $last[1] === '/') { + array_pop($args); + $args[] = $last[2]; + $args[] = $last[3]; + } + return $args; + } + + + /** + * Make sure a color's components don't go out of bounds + * + * @param array $c + * + * @return array + */ + protected function fixColor($c) + { + foreach ([1, 2, 3] as $i) { + if ($c[$i] < 0) { + $c[$i] = 0; + } + + if ($c[$i] > 255) { + $c[$i] = 255; + } + + if (!\is_int($c[$i])) { + $c[$i] = round($c[$i]); + } + } + + return $c; + } + + /** + * Convert RGB to HSL + * + * @internal + * + * @param integer $red + * @param integer $green + * @param integer $blue + * + * @return array + */ + public function toHSL($red, $green, $blue) + { + $min = min($red, $green, $blue); + $max = max($red, $green, $blue); + + $l = $min + $max; + $d = $max - $min; + + if ((int) $d === 0) { + $h = $s = 0; + } else { + if ($l < 255) { + $s = $d / $l; + } else { + $s = $d / (510 - $l); + } + + if ($red == $max) { + $h = 60 * ($green - $blue) / $d; + } elseif ($green == $max) { + $h = 60 * ($blue - $red) / $d + 120; + } elseif ($blue == $max) { + $h = 60 * ($red - $green) / $d + 240; + } + } + + return [Type::T_HSL, fmod($h + 360, 360), $s * 100, $l / 5.1]; + } + + /** + * Hue to RGB helper + * + * @param float $m1 + * @param float $m2 + * @param float $h + * + * @return float + */ + protected function hueToRGB($m1, $m2, $h) + { + if ($h < 0) { + $h += 1; + } elseif ($h > 1) { + $h -= 1; + } + + if ($h * 6 < 1) { + return $m1 + ($m2 - $m1) * $h * 6; + } + + if ($h * 2 < 1) { + return $m2; + } + + if ($h * 3 < 2) { + return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6; + } + + return $m1; + } + + /** + * Convert HSL to RGB + * + * @internal + * + * @param int|float $hue H from 0 to 360 + * @param int|float $saturation S from 0 to 100 + * @param int|float $lightness L from 0 to 100 + * + * @return array + */ + public function toRGB($hue, $saturation, $lightness) + { + if ($hue < 0) { + $hue += 360; + } + + $h = $hue / 360; + $s = min(100, max(0, $saturation)) / 100; + $l = min(100, max(0, $lightness)) / 100; + + $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s; + $m1 = $l * 2 - $m2; + + $r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255; + $g = $this->hueToRGB($m1, $m2, $h) * 255; + $b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255; + + $out = [Type::T_COLOR, $r, $g, $b]; + + return $out; + } + + /** + * Convert HWB to RGB + * https://www.w3.org/TR/css-color-4/#hwb-to-rgb + * + * @api + * + * @param integer $hue H from 0 to 360 + * @param integer $whiteness W from 0 to 100 + * @param integer $blackness B from 0 to 100 + * + * @return array + */ + private function HWBtoRGB($hue, $whiteness, $blackness) + { + $w = min(100, max(0, $whiteness)) / 100; + $b = min(100, max(0, $blackness)) / 100; + + $sum = $w + $b; + if ($sum > 1.0) { + $w = $w / $sum; + $b = $b / $sum; + } + $b = min(1.0 - $w, $b); + + $rgb = $this->toRGB($hue, 100, 50); + for($i = 1; $i < 4; $i++) { + $rgb[$i] *= (1.0 - $w - $b); + $rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001); + } + + return $rgb; + } + + /** + * Convert RGB to HWB + * + * @api + * + * @param integer $red + * @param integer $green + * @param integer $blue + * + * @return array + */ + private function RGBtoHWB($red, $green, $blue) + { + $min = min($red, $green, $blue); + $max = max($red, $green, $blue); + + $d = $max - $min; + + if ((int) $d === 0) { + $h = 0; + } else { + + if ($red == $max) { + $h = 60 * ($green - $blue) / $d; + } elseif ($green == $max) { + $h = 60 * ($blue - $red) / $d + 120; + } elseif ($blue == $max) { + $h = 60 * ($red - $green) / $d + 240; + } + } + + return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 *100]; + } + + + // Built in functions + + protected static $libCall = ['function', 'args...']; + protected function libCall($args) + { + $functionReference = $args[0]; + + if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) { + $name = $this->compileStringContent($this->coerceString($functionReference)); + $warning = "Passing a string to call() is deprecated and will be illegal\n" + . "in Sass 4.0. Use call(function-reference($name)) instead."; + Warn::deprecation($warning); + $functionReference = $this->libGetFunction([$this->assertString($functionReference, 'function')]); + } + + if ($functionReference === static::$null) { + return static::$null; + } + + if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) { + throw $this->error('Function reference expected, got ' . $functionReference[0]); + } + + $callArgs = [ + [null, $args[1], true] + ]; + + return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]); + } + + + protected static $libGetFunction = [ + ['name'], + ['name', 'css'] + ]; + protected function libGetFunction($args) + { + $name = $this->compileStringContent($this->assertString(array_shift($args), 'name')); + $isCss = false; + + if (count($args)) { + $isCss = array_shift($args); + $isCss = (($isCss === static::$true) ? true : false); + } + + if ($isCss) { + return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]]; + } + + return $this->getFunctionReference($name, true); + } + + protected static $libIf = ['condition', 'if-true', 'if-false:']; + protected function libIf($args) + { + list($cond, $t, $f) = $args; + + if (! $this->isTruthy($this->reduce($cond, true))) { + return $this->reduce($f, true); + } + + return $this->reduce($t, true); + } + + protected static $libIndex = ['list', 'value']; + protected function libIndex($args) + { + list($list, $value) = $args; + + if ( + $list[0] === Type::T_MAP || + $list[0] === Type::T_STRING || + $list[0] === Type::T_KEYWORD || + $list[0] === Type::T_INTERPOLATE + ) { + $list = $this->coerceList($list, ' '); + } + + if ($list[0] !== Type::T_LIST) { + return static::$null; + } + + // Numbers are represented with value objects, for which the PHP equality operator does not + // match the Sass rules (and we cannot overload it). As they are the only type of values + // represented with a value object for now, they require a special case. + if ($value instanceof Number) { + $key = 0; + foreach ($list[2] as $item) { + $key++; + $itemValue = $this->normalizeValue($item); + + if ($itemValue instanceof Number && $value->equals($itemValue)) { + return new Number($key, ''); + } + } + return static::$null; + } + + $values = []; + + + foreach ($list[2] as $item) { + $values[] = $this->normalizeValue($item); + } + + $key = array_search($this->normalizeValue($value), $values); + + return false === $key ? static::$null : new Number($key + 1, ''); + } + + protected static $libRgb = [ + ['color'], + ['color', 'alpha'], + ['channels'], + ['red', 'green', 'blue'], + ['red', 'green', 'blue', 'alpha'] ]; + protected function libRgb($args, $kwargs, $funcName = 'rgb') + { + switch (\count($args)) { + case 1: + if (! $color = $this->coerceColor($args[0], true)) { + $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']]; + } + break; + + case 3: + $color = [Type::T_COLOR, $args[0], $args[1], $args[2]]; + + if (! $color = $this->coerceColor($color)) { + $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']]; + } + + return $color; + + case 2: + if ($color = $this->coerceColor($args[0], true)) { + $alpha = $this->compileRGBAValue($args[1], true); + + if (is_numeric($alpha)) { + $color[4] = $alpha; + } else { + $color = [Type::T_STRING, '', + [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']]; + } + } else { + $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ')']]; + } + break; + + case 4: + default: + $color = [Type::T_COLOR, $args[0], $args[1], $args[2], $args[3]]; + + if (! $color = $this->coerceColor($color)) { + $color = [Type::T_STRING, '', + [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']]; + } + break; + } + + return $color; + } + + protected static $libRgba = [ + ['color'], + ['color', 'alpha'], + ['channels'], + ['red', 'green', 'blue'], + ['red', 'green', 'blue', 'alpha'] ]; + protected function libRgba($args, $kwargs) + { + return $this->libRgb($args, $kwargs, 'rgba'); + } + + /** + * Helper function for adjust_color, change_color, and scale_color + * + * @param array $args + * @param string $operation + * @param callable $fn + * + * @return array + * + * @phpstan-param callable(float|int, float|int|null, float|int): (float|int) $fn + */ + protected function alterColor(array $args, $operation, $fn) + { + $color = $this->assertColor($args[0], 'color'); + + if ($args[1][2]) { + throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.'); + } + + $kwargs = $this->getArgumentListKeywords($args[1]); + + $scale = $operation === 'scale'; + $change = $operation === 'change'; + + /** + * @param string $name + * @param float|int $max + * @param bool $checkPercent + * @param bool $assertPercent + * + * @return float|int|null + */ + $getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) { + if (!isset($kwargs[$name])) { + return null; + } + + $number = $this->assertNumber($kwargs[$name], $name); + unset($kwargs[$name]); + + if (!$scale && $checkPercent) { + if (!$number->hasUnit('%')) { + $warning = $this->error("{$name} Passing a number `$number` without unit % is deprecated."); + $this->logger->warn($warning->getMessage(), true); + } + } + + if ($scale || $assertPercent) { + $number->assertUnit('%', $name); + } + + if ($scale) { + $max = 100; + } + + return $number->valueInRange($change ? 0 : -$max, $max, $name); + }; + + $alpha = $getParam('alpha', 1); + $red = $getParam('red', 255); + $green = $getParam('green', 255); + $blue = $getParam('blue', 255); + + if ($scale || !isset($kwargs['hue'])) { + $hue = null; + } else { + $hueNumber = $this->assertNumber($kwargs['hue'], 'hue'); + unset($kwargs['hue']); + $hue = $hueNumber->getDimension(); + } + $saturation = $getParam('saturation', 100, true); + $lightness = $getParam('lightness', 100, true); + $whiteness = $getParam('whiteness', 100, false, true); + $blackness = $getParam('blackness', 100, false, true); + + if (!empty($kwargs)) { + $unknownNames = array_keys($kwargs); + $lastName = array_pop($unknownNames); + $message = sprintf( + 'No argument%s named $%s%s.', + $unknownNames ? 's' : '', + $unknownNames ? implode(', $', $unknownNames) . ' or $' : '', + $lastName + ); + throw new SassScriptException($message); + } + + $hasRgb = $red !== null || $green !== null || $blue !== null; + $hasSL = $saturation !== null || $lightness !== null; + $hasWB = $whiteness !== null || $blackness !== null; + $found = false; + + if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) { + throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.', $hasWB ? 'HWB' : 'HSL')); + } + + if ($hasWB && $hasSL) { + throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.'); + } + + if ($hasRgb) { + $color[1] = round($fn($color[1], $red, 255)); + $color[2] = round($fn($color[2], $green, 255)); + $color[3] = round($fn($color[3], $blue, 255)); + } elseif ($hasWB) { + $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]); + if ($hue !== null) { + $hwb[1] = $change ? $hue : $hwb[1] + $hue; + } + $hwb[2] = $fn($hwb[2], $whiteness, 100); + $hwb[3] = $fn($hwb[3], $blackness, 100); + + $rgb = $this->HWBtoRGB($hwb[1], $hwb[2], $hwb[3]); + + if (isset($color[4])) { + $rgb[4] = $color[4]; + } + + $color = $rgb; + } elseif ($hue !== null || $hasSL) { + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + + if ($hue !== null) { + $hsl[1] = $change ? $hue : $hsl[1] + $hue; + } + $hsl[2] = $fn($hsl[2], $saturation, 100); + $hsl[3] = $fn($hsl[3], $lightness, 100); + + $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); + + if (isset($color[4])) { + $rgb[4] = $color[4]; + } + + $color = $rgb; + } + + if ($alpha !== null) { + $existingAlpha = isset($color[4]) ? $color[4] : 1; + $color[4] = $fn($existingAlpha, $alpha, 1); + } + + return $color; + } + + protected static $libAdjustColor = ['color', 'kwargs...']; + protected function libAdjustColor($args) + { + return $this->alterColor($args, 'adjust', function ($base, $alter, $max) { + if ($alter === null) { + return $base; + } + + $new = $base + $alter; + + if ($new < 0) { + return 0; + } + + if ($new > $max) { + return $max; + } + + return $new; + }); + } + + protected static $libChangeColor = ['color', 'kwargs...']; + protected function libChangeColor($args) + { + return $this->alterColor($args,'change', function ($base, $alter, $max) { + if ($alter === null) { + return $base; + } + + return $alter; + }); + } + + protected static $libScaleColor = ['color', 'kwargs...']; + protected function libScaleColor($args) + { + return $this->alterColor($args, 'scale', function ($base, $scale, $max) { + if ($scale === null) { + return $base; + } + + $scale = $scale / 100; + + if ($scale < 0) { + return $base * $scale + $base; + } + + return ($max - $base) * $scale + $base; + }); + } + + protected static $libIeHexStr = ['color']; + protected function libIeHexStr($args) + { + $color = $this->coerceColor($args[0]); + + if (\is_null($color)) { + throw $this->error('Error: argument `$color` of `ie-hex-str($color)` must be a color'); + } + + $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255; + + return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]]; + } + + protected static $libRed = ['color']; + protected function libRed($args) + { + $color = $this->coerceColor($args[0]); + + if (\is_null($color)) { + throw $this->error('Error: argument `$color` of `red($color)` must be a color'); + } + + return new Number((int) $color[1], ''); + } + + protected static $libGreen = ['color']; + protected function libGreen($args) + { + $color = $this->coerceColor($args[0]); + + if (\is_null($color)) { + throw $this->error('Error: argument `$color` of `green($color)` must be a color'); + } + + return new Number((int) $color[2], ''); + } + + protected static $libBlue = ['color']; + protected function libBlue($args) + { + $color = $this->coerceColor($args[0]); + + if (\is_null($color)) { + throw $this->error('Error: argument `$color` of `blue($color)` must be a color'); + } + + return new Number((int) $color[3], ''); + } + + protected static $libAlpha = ['color']; + protected function libAlpha($args) + { + if ($color = $this->coerceColor($args[0])) { + return new Number(isset($color[4]) ? $color[4] : 1, ''); + } + + // this might be the IE function, so return value unchanged + return null; + } + + protected static $libOpacity = ['color']; + protected function libOpacity($args) + { + $value = $args[0]; + + if ($value instanceof Number) { + return null; + } + + return $this->libAlpha($args); + } + + // mix two colors + protected static $libMix = [ + ['color1', 'color2', 'weight:50%'], + ['color-1', 'color-2', 'weight:50%'] + ]; + protected function libMix($args) + { + list($first, $second, $weight) = $args; + + $first = $this->assertColor($first, 'color1'); + $second = $this->assertColor($second, 'color2'); + $weightScale = $this->assertNumber($weight, 'weight')->valueInRange(0, 100, 'weight') / 100; + + $firstAlpha = isset($first[4]) ? $first[4] : 1; + $secondAlpha = isset($second[4]) ? $second[4] : 1; + + $normalizedWeight = $weightScale * 2 - 1; + $alphaDistance = $firstAlpha - $secondAlpha; + + $combinedWeight = $normalizedWeight * $alphaDistance == -1 ? $normalizedWeight : ($normalizedWeight + $alphaDistance) / (1 + $normalizedWeight * $alphaDistance); + $weight1 = ($combinedWeight + 1) / 2.0; + $weight2 = 1.0 - $weight1; + + $new = [Type::T_COLOR, + $weight1 * $first[1] + $weight2 * $second[1], + $weight1 * $first[2] + $weight2 * $second[2], + $weight1 * $first[3] + $weight2 * $second[3], + ]; + + if ($firstAlpha != 1.0 || $secondAlpha != 1.0) { + $new[] = $firstAlpha * $weightScale + $secondAlpha * (1 - $weightScale); + } + + return $this->fixColor($new); + } + + protected static $libHsl = [ + ['channels'], + ['hue', 'saturation'], + ['hue', 'saturation', 'lightness'], + ['hue', 'saturation', 'lightness', 'alpha'] ]; + protected function libHsl($args, $kwargs, $funcName = 'hsl') + { + $args_to_check = $args; + + if (\count($args) == 1) { + if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) { + return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']]; + } + + $args = $args[0][2]; + $args_to_check = $kwargs['channels'][2]; + } + + if (\count($args) === 2) { + // if var() is used as an argument, return as a css function + foreach ($args as $arg) { + if ($arg[0] === Type::T_FUNCTION && in_array($arg[1], ['var'])) { + return null; + } + } + + throw new SassScriptException('Missing argument $lightness.'); + } + + foreach ($kwargs as $k => $arg) { + if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) { + return null; + } + } + + foreach ($args_to_check as $k => $arg) { + if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) { + if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) { + return null; + } + + $args[$k] = $this->stringifyFncallArgs($arg); + } + + if ( + $k >= 2 && count($args) === 4 && + in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && + in_array($arg[1], ['calc','env']) + ) { + return null; + } + } + + $hue = $this->reduce($args[0]); + $saturation = $this->reduce($args[1]); + $lightness = $this->reduce($args[2]); + $alpha = null; + + if (\count($args) === 4) { + $alpha = $this->compileColorPartValue($args[3], 0, 100, false); + + if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number || ! is_numeric($alpha)) { + return [Type::T_STRING, '', + [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']]; + } + } else { + if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number) { + return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']]; + } + } + + $hueValue = fmod($hue->getDimension(), 360); + + while ($hueValue < 0) { + $hueValue += 360; + } + + $color = $this->toRGB($hueValue, max(0, min($saturation->getDimension(), 100)), max(0, min($lightness->getDimension(), 100))); + + if (! \is_null($alpha)) { + $color[4] = $alpha; + } + + return $color; + } + + protected static $libHsla = [ + ['channels'], + ['hue', 'saturation'], + ['hue', 'saturation', 'lightness'], + ['hue', 'saturation', 'lightness', 'alpha']]; + protected function libHsla($args, $kwargs) + { + return $this->libHsl($args, $kwargs, 'hsla'); + } + + protected static $libHue = ['color']; + protected function libHue($args) + { + $color = $this->assertColor($args[0], 'color'); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + + return new Number($hsl[1], 'deg'); + } + + protected static $libSaturation = ['color']; + protected function libSaturation($args) + { + $color = $this->assertColor($args[0], 'color'); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + + return new Number($hsl[2], '%'); + } + + protected static $libLightness = ['color']; + protected function libLightness($args) + { + $color = $this->assertColor($args[0], 'color'); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + + return new Number($hsl[3], '%'); + } + + /* + * Todo : a integrer dans le futur module color + protected static $libHwb = [ + ['channels'], + ['hue', 'whiteness', 'blackness'], + ['hue', 'whiteness', 'blackness', 'alpha'] ]; + protected function libHwb($args, $kwargs, $funcName = 'hwb') + { + $args_to_check = $args; + + if (\count($args) == 1) { + if ($args[0][0] !== Type::T_LIST) { + throw $this->error("Missing elements \$whiteness and \$blackness"); + } + + if (\trim($args[0][1])) { + throw $this->error("\$channels must be a space-separated list."); + } + + if (! empty($args[0]['enclosing'])) { + throw $this->error("\$channels must be an unbracketed list."); + } + + $args = $args[0][2]; + if (\count($args) > 3) { + throw $this->error("hwb() : Only 3 elements are allowed but ". \count($args) . "were passed"); + } + + $args_to_check = $this->extractSlashAlphaInColorFunction($kwargs['channels'][2]); + if (\count($args_to_check) !== \count($kwargs['channels'][2])) { + $args = $args_to_check; + } + } + + if (\count($args_to_check) < 2) { + throw $this->error("Missing elements \$whiteness and \$blackness"); + } + if (\count($args_to_check) < 3) { + throw $this->error("Missing element \$blackness"); + } + if (\count($args_to_check) > 4) { + throw $this->error("hwb() : Only 4 elements are allowed but ". \count($args) . "were passed"); + } + + foreach ($kwargs as $k => $arg) { + if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) { + return null; + } + } + + foreach ($args_to_check as $k => $arg) { + if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) { + if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) { + return null; + } + + $args[$k] = $this->stringifyFncallArgs($arg); + } + + if ( + $k >= 2 && count($args) === 4 && + in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && + in_array($arg[1], ['calc','env']) + ) { + return null; + } + } + + $hue = $this->reduce($args[0]); + $whiteness = $this->reduce($args[1]); + $blackness = $this->reduce($args[2]); + $alpha = null; + + if (\count($args) === 4) { + $alpha = $this->compileColorPartValue($args[3], 0, 1, false); + + if (! \is_numeric($alpha)) { + $val = $this->compileValue($args[3]); + throw $this->error("\$alpha: $val is not a number"); + } + } + + $this->assertNumber($hue, 'hue'); + $this->assertUnit($whiteness, ['%'], 'whiteness'); + $this->assertUnit($blackness, ['%'], 'blackness'); + + $this->assertRange($whiteness, 0, 100, "0% and 100%", "whiteness"); + $this->assertRange($blackness, 0, 100, "0% and 100%", "blackness"); + + $w = $whiteness->getDimension(); + $b = $blackness->getDimension(); + + $hueValue = $hue->getDimension() % 360; + + while ($hueValue < 0) { + $hueValue += 360; + } + + $color = $this->HWBtoRGB($hueValue, $w, $b); + + if (! \is_null($alpha)) { + $color[4] = $alpha; + } + + return $color; + } + + protected static $libWhiteness = ['color']; + protected function libWhiteness($args, $kwargs, $funcName = 'whiteness') { + + $color = $this->assertColor($args[0]); + $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]); + + return new Number($hwb[2], '%'); + } + + protected static $libBlackness = ['color']; + protected function libBlackness($args, $kwargs, $funcName = 'blackness') { + + $color = $this->assertColor($args[0]); + $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]); + + return new Number($hwb[3], '%'); + } + */ + + protected function adjustHsl($color, $idx, $amount) + { + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + $hsl[$idx] += $amount; + + if ($idx !== 1) { + // Clamp the saturation and lightness + $hsl[$idx] = min(max(0, $hsl[$idx]), 100); + } + + $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); + + if (isset($color[4])) { + $out[4] = $color[4]; + } + + return $out; + } + + protected static $libAdjustHue = ['color', 'degrees']; + protected function libAdjustHue($args) + { + $color = $this->assertColor($args[0], 'color'); + $degrees = $this->assertNumber($args[1], 'degrees')->getDimension(); + + return $this->adjustHsl($color, 1, $degrees); + } + + protected static $libLighten = ['color', 'amount']; + protected function libLighten($args) + { + $color = $this->assertColor($args[0], 'color'); + $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%'); + + return $this->adjustHsl($color, 3, $amount); + } + + protected static $libDarken = ['color', 'amount']; + protected function libDarken($args) + { + $color = $this->assertColor($args[0], 'color'); + $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%'); + + return $this->adjustHsl($color, 3, -$amount); + } + + protected static $libSaturate = [['color', 'amount'], ['amount']]; + protected function libSaturate($args) + { + $value = $args[0]; + + if (count($args) === 1) { + $this->assertNumber($args[0], 'amount'); + + return null; + } + + $color = $this->assertColor($args[0], 'color'); + $amount = $this->assertNumber($args[1], 'amount'); + + return $this->adjustHsl($color, 2, $amount->valueInRange(0, 100, 'amount')); + } + + protected static $libDesaturate = ['color', 'amount']; + protected function libDesaturate($args) + { + $color = $this->assertColor($args[0], 'color'); + $amount = $this->assertNumber($args[1], 'amount'); + + return $this->adjustHsl($color, 2, -$amount->valueInRange(0, 100, 'amount')); + } + + protected static $libGrayscale = ['color']; + protected function libGrayscale($args) + { + $value = $args[0]; + + if ($value instanceof Number) { + return null; + } + + return $this->adjustHsl($this->assertColor($value, 'color'), 2, -100); + } + + protected static $libComplement = ['color']; + protected function libComplement($args) + { + return $this->adjustHsl($this->assertColor($args[0], 'color'), 1, 180); + } + + protected static $libInvert = ['color', 'weight:100%']; + protected function libInvert($args) + { + $value = $args[0]; + + $weight = $this->assertNumber($args[1], 'weight'); + + if ($value instanceof Number) { + if ($weight->getDimension() != 100 || !$weight->hasUnit('%')) { + throw new SassScriptException('Only one argument may be passed to the plain-CSS invert() function.'); + } + + return null; + } + + $color = $this->assertColor($value, 'color'); + $inverted = $color; + $inverted[1] = 255 - $inverted[1]; + $inverted[2] = 255 - $inverted[2]; + $inverted[3] = 255 - $inverted[3]; + + return $this->libMix([$inverted, $color, $weight]); + } + + // increases opacity by amount + protected static $libOpacify = ['color', 'amount']; + protected function libOpacify($args) + { + $color = $this->assertColor($args[0], 'color'); + $amount = $this->assertNumber($args[1], 'amount'); + + $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRange(0, 1, 'amount'); + $color[4] = min(1, max(0, $color[4])); + + return $color; + } + + protected static $libFadeIn = ['color', 'amount']; + protected function libFadeIn($args) + { + return $this->libOpacify($args); + } + + // decreases opacity by amount + protected static $libTransparentize = ['color', 'amount']; + protected function libTransparentize($args) + { + $color = $this->assertColor($args[0], 'color'); + $amount = $this->assertNumber($args[1], 'amount'); + + $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRange(0, 1, 'amount'); + $color[4] = min(1, max(0, $color[4])); + + return $color; + } + + protected static $libFadeOut = ['color', 'amount']; + protected function libFadeOut($args) + { + return $this->libTransparentize($args); + } + + protected static $libUnquote = ['string']; + protected function libUnquote($args) + { + try { + $str = $this->assertString($args[0], 'string'); + } catch (SassScriptException $e) { + $value = $this->compileValue($args[0]); + $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]); + $line = $this->sourceLine; + + $message = "Passing $value, a non-string value, to unquote() +will be an error in future versions of Sass.\n on line $line of $fname"; + + $this->logger->warn($message, true); + + return $args[0]; + } + + $str[1] = ''; + + return $str; + } + + protected static $libQuote = ['string']; + protected function libQuote($args) + { + $value = $this->assertString($args[0], 'string'); + + $value[1] = '"'; + + return $value; + } + + protected static $libPercentage = ['number']; + protected function libPercentage($args) + { + $num = $this->assertNumber($args[0], 'number'); + $num->assertNoUnits('number'); + + return new Number($num->getDimension() * 100, '%'); + } + + protected static $libRound = ['number']; + protected function libRound($args) + { + $num = $this->assertNumber($args[0], 'number'); + + return new Number(round($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits()); + } + + protected static $libFloor = ['number']; + protected function libFloor($args) + { + $num = $this->assertNumber($args[0], 'number'); + + return new Number(floor($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits()); + } + + protected static $libCeil = ['number']; + protected function libCeil($args) + { + $num = $this->assertNumber($args[0], 'number'); + + return new Number(ceil($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits()); + } + + protected static $libAbs = ['number']; + protected function libAbs($args) + { + $num = $this->assertNumber($args[0], 'number'); + + return new Number(abs($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits()); + } + + protected static $libMin = ['numbers...']; + protected function libMin($args) + { + /** + * @var Number|null + */ + $min = null; + + foreach ($args[0][2] as $arg) { + $number = $this->assertNumber($arg); + + if (\is_null($min) || $min->greaterThan($number)) { + $min = $number; + } + } + + if (!\is_null($min)) { + return $min; + } + + throw $this->error('At least one argument must be passed.'); + } + + protected static $libMax = ['numbers...']; + protected function libMax($args) + { + /** + * @var Number|null + */ + $max = null; + + foreach ($args[0][2] as $arg) { + $number = $this->assertNumber($arg); + + if (\is_null($max) || $max->lessThan($number)) { + $max = $number; + } + } + + if (!\is_null($max)) { + return $max; + } + + throw $this->error('At least one argument must be passed.'); + } + + protected static $libLength = ['list']; + protected function libLength($args) + { + $list = $this->coerceList($args[0], ',', true); + + return new Number(\count($list[2]), ''); + } + + protected static $libListSeparator = ['list']; + protected function libListSeparator($args) + { + if (! \in_array($args[0][0], [Type::T_LIST, Type::T_MAP])) { + return [Type::T_KEYWORD, 'space']; + } + + $list = $this->coerceList($args[0]); + + if (\count($list[2]) <= 1 && empty($list['enclosing'])) { + return [Type::T_KEYWORD, 'space']; + } + + if ($list[1] === ',') { + return [Type::T_KEYWORD, 'comma']; + } + + return [Type::T_KEYWORD, 'space']; + } + + protected static $libNth = ['list', 'n']; + protected function libNth($args) + { + $list = $this->coerceList($args[0], ',', false); + $n = $this->assertNumber($args[1])->getDimension(); + + if ($n > 0) { + $n--; + } elseif ($n < 0) { + $n += \count($list[2]); + } + + return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue; + } + + protected static $libSetNth = ['list', 'n', 'value']; + protected function libSetNth($args) + { + $list = $this->coerceList($args[0]); + $n = $this->assertNumber($args[1])->getDimension(); + + if ($n > 0) { + $n--; + } elseif ($n < 0) { + $n += \count($list[2]); + } + + if (! isset($list[2][$n])) { + throw $this->error('Invalid argument for "n"'); + } + + $list[2][$n] = $args[2]; + + return $list; + } + + protected static $libMapGet = ['map', 'key']; + protected function libMapGet($args) + { + $map = $this->assertMap($args[0], 'map'); + $key = $args[1]; + + if (! \is_null($key)) { + $key = $this->compileStringContent($this->coerceString($key)); + + for ($i = \count($map[1]) - 1; $i >= 0; $i--) { + if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) { + return $map[2][$i]; + } + } + } + + return static::$null; + } + + protected static $libMapKeys = ['map']; + protected function libMapKeys($args) + { + $map = $this->assertMap($args[0], 'map'); + $keys = $map[1]; + + return [Type::T_LIST, ',', $keys]; + } + + protected static $libMapValues = ['map']; + protected function libMapValues($args) + { + $map = $this->assertMap($args[0], 'map'); + $values = $map[2]; + + return [Type::T_LIST, ',', $values]; + } + + protected static $libMapRemove = [ + ['map'], + ['map', 'key', 'keys...'], + ]; + protected function libMapRemove($args) + { + $map = $this->assertMap($args[0], 'map'); + + if (\count($args) === 1) { + return $map; + } + + $keys = []; + $keys[] = $this->compileStringContent($this->coerceString($args[1])); + + foreach ($args[2][2] as $key) { + $keys[] = $this->compileStringContent($this->coerceString($key)); + } + + for ($i = \count($map[1]) - 1; $i >= 0; $i--) { + if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) { + array_splice($map[1], $i, 1); + array_splice($map[2], $i, 1); + } + } + + return $map; + } + + protected static $libMapHasKey = ['map', 'key']; + protected function libMapHasKey($args) + { + $map = $this->assertMap($args[0], 'map'); + + return $this->toBool($this->mapHasKey($map, $args[1])); + } + + /** + * @param array|Number $keyValue + * + * @return bool + */ + private function mapHasKey(array $map, $keyValue) + { + $key = $this->compileStringContent($this->coerceString($keyValue)); + + for ($i = \count($map[1]) - 1; $i >= 0; $i--) { + if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) { + return true; + } + } + + return false; + } + + protected static $libMapMerge = [ + ['map1', 'map2'], + ['map-1', 'map-2'] + ]; + protected function libMapMerge($args) + { + $map1 = $this->assertMap($args[0], 'map1'); + $map2 = $this->assertMap($args[1], 'map2'); + + foreach ($map2[1] as $i2 => $key2) { + $key = $this->compileStringContent($this->coerceString($key2)); + + foreach ($map1[1] as $i1 => $key1) { + if ($key === $this->compileStringContent($this->coerceString($key1))) { + $map1[2][$i1] = $map2[2][$i2]; + continue 2; + } + } + + $map1[1][] = $map2[1][$i2]; + $map1[2][] = $map2[2][$i2]; + } + + return $map1; + } + + protected static $libKeywords = ['args']; + protected function libKeywords($args) + { + $value = $args[0]; + + if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) { + $compiledValue = $this->compileValue($value); + + throw SassScriptException::forArgument($compiledValue . ' is not an argument list.', 'args'); + } + + $keys = []; + $values = []; + + foreach ($this->getArgumentListKeywords($value) as $name => $arg) { + $keys[] = [Type::T_KEYWORD, $name]; + $values[] = $arg; + } + + return [Type::T_MAP, $keys, $values]; + } + + protected static $libIsBracketed = ['list']; + protected function libIsBracketed($args) + { + $list = $args[0]; + $this->coerceList($list, ' '); + + if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') { + return self::$true; + } + + return self::$false; + } + + /** + * @param array $list1 + * @param array|Number|null $sep + * + * @return string + * @throws CompilerException + */ + protected function listSeparatorForJoin($list1, $sep) + { + if (! isset($sep)) { + return $list1[1]; + } + + switch ($this->compileValue($sep)) { + case 'comma': + return ','; + + case 'space': + return ' '; + + default: + return $list1[1]; + } + } + + protected static $libJoin = ['list1', 'list2', 'separator:null', 'bracketed:auto']; + protected function libJoin($args) + { + list($list1, $list2, $sep, $bracketed) = $args; + + $list1 = $this->coerceList($list1, ' ', true); + $list2 = $this->coerceList($list2, ' ', true); + $sep = $this->listSeparatorForJoin($list1, $sep); + + if ($bracketed === static::$true) { + $bracketed = true; + } elseif ($bracketed === static::$false) { + $bracketed = false; + } elseif ($bracketed === [Type::T_KEYWORD, 'auto']) { + $bracketed = 'auto'; + } elseif ($bracketed === static::$null) { + $bracketed = false; + } else { + $bracketed = $this->compileValue($bracketed); + $bracketed = ! ! $bracketed; + + if ($bracketed === true) { + $bracketed = true; + } + } + + if ($bracketed === 'auto') { + $bracketed = false; + + if (! empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') { + $bracketed = true; + } + } + + $res = [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])]; + + if (isset($list1['enclosing'])) { + $res['enlcosing'] = $list1['enclosing']; + } + + if ($bracketed) { + $res['enclosing'] = 'bracket'; + } + + return $res; + } + + protected static $libAppend = ['list', 'val', 'separator:null']; + protected function libAppend($args) + { + list($list1, $value, $sep) = $args; + + $list1 = $this->coerceList($list1, ' ', true); + $sep = $this->listSeparatorForJoin($list1, $sep); + $res = [Type::T_LIST, $sep, array_merge($list1[2], [$value])]; + + if (isset($list1['enclosing'])) { + $res['enclosing'] = $list1['enclosing']; + } + + return $res; + } + + protected static $libZip = ['lists...']; + protected function libZip($args) + { + $argLists = []; + foreach ($args[0][2] as $arg) { + $argLists[] = $this->coerceList($arg); + } + + $lists = []; + $firstList = array_shift($argLists); + + $result = [Type::T_LIST, ',', $lists]; + if (! \is_null($firstList)) { + foreach ($firstList[2] as $key => $item) { + $list = [Type::T_LIST, '', [$item]]; + + foreach ($argLists as $arg) { + if (isset($arg[2][$key])) { + $list[2][] = $arg[2][$key]; + } else { + break 2; + } + } + + $lists[] = $list; + } + + $result[2] = $lists; + } else { + $result['enclosing'] = 'parent'; + } + + return $result; + } + + protected static $libTypeOf = ['value']; + protected function libTypeOf($args) + { + $value = $args[0]; + + return [Type::T_KEYWORD, $this->getTypeOf($value)]; + } + + /** + * @param array|Number $value + * + * @return string + */ + private function getTypeOf($value) + { + switch ($value[0]) { + case Type::T_KEYWORD: + if ($value === static::$true || $value === static::$false) { + return 'bool'; + } + + if ($this->coerceColor($value)) { + return 'color'; + } + + // fall-thru + case Type::T_FUNCTION: + return 'string'; + + case Type::T_FUNCTION_REFERENCE: + return 'function'; + + case Type::T_LIST: + if (isset($value[3]) && \is_array($value[3])) { + return 'arglist'; + } + + // fall-thru + default: + return $value[0]; + } + } + + protected static $libUnit = ['number']; + protected function libUnit($args) + { + $num = $this->assertNumber($args[0], 'number'); + + return [Type::T_STRING, '"', [$num->unitStr()]]; + } + + protected static $libUnitless = ['number']; + protected function libUnitless($args) + { + $value = $this->assertNumber($args[0], 'number'); + + return $this->toBool($value->unitless()); + } + + protected static $libComparable = [ + ['number1', 'number2'], + ['number-1', 'number-2'] + ]; + protected function libComparable($args) + { + list($number1, $number2) = $args; + + if ( + ! $number1 instanceof Number || + ! $number2 instanceof Number + ) { + throw $this->error('Invalid argument(s) for "comparable"'); + } + + return $this->toBool($number1->isComparableTo($number2)); + } + + protected static $libStrIndex = ['string', 'substring']; + protected function libStrIndex($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + $substring = $this->assertString($args[1], 'substring'); + $substringContent = $this->compileStringContent($substring); + + if (! \strlen($substringContent)) { + $result = 0; + } else { + $result = Util::mbStrpos($stringContent, $substringContent); + } + + return $result === false ? static::$null : new Number($result + 1, ''); + } + + protected static $libStrInsert = ['string', 'insert', 'index']; + protected function libStrInsert($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + $insert = $this->assertString($args[1], 'insert'); + $insertContent = $this->compileStringContent($insert); + + $index = $this->assertInteger($args[2], 'index'); + if ($index > 0) { + $index = $index - 1; + } + if ($index < 0) { + $index = Util::mbStrlen($stringContent) + 1 + $index; + } + + $string[2] = [ + Util::mbSubstr($stringContent, 0, $index), + $insertContent, + Util::mbSubstr($stringContent, $index) + ]; + + return $string; + } + + protected static $libStrLength = ['string']; + protected function libStrLength($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + return new Number(Util::mbStrlen($stringContent), ''); + } + + protected static $libStrSlice = ['string', 'start-at', 'end-at:-1']; + protected function libStrSlice($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + $start = $this->assertNumber($args[1], 'start-at'); + $start->assertNoUnits('start-at'); + $startInt = $this->assertInteger($start, 'start-at'); + $end = $this->assertNumber($args[2], 'end-at'); + $end->assertNoUnits('end-at'); + $endInt = $this->assertInteger($end, 'end-at'); + + if ($endInt === 0) { + return [Type::T_STRING, $string[1], []]; + } + + if ($startInt > 0) { + $startInt--; + } + + if ($endInt < 0) { + $endInt = Util::mbStrlen($stringContent) + $endInt; + } else { + $endInt--; + } + + if ($endInt < $startInt) { + return [Type::T_STRING, $string[1], []]; + } + + $length = $endInt - $startInt + 1; // The end of the slice is inclusive + + $string[2] = [Util::mbSubstr($stringContent, $startInt, $length)]; + + return $string; + } + + protected static $libToLowerCase = ['string']; + protected function libToLowerCase($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtolower')]; + + return $string; + } + + protected static $libToUpperCase = ['string']; + protected function libToUpperCase($args) + { + $string = $this->assertString($args[0], 'string'); + $stringContent = $this->compileStringContent($string); + + $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtoupper')]; + + return $string; + } + + /** + * Apply a filter on a string content, only on ascii chars + * let extended chars untouched + * + * @param string $stringContent + * @param callable $filter + * @return string + */ + protected function stringTransformAsciiOnly($stringContent, $filter) + { + $mblength = Util::mbStrlen($stringContent); + if ($mblength === strlen($stringContent)) { + return $filter($stringContent); + } + $filteredString = ""; + for ($i = 0; $i < $mblength; $i++) { + $char = Util::mbSubstr($stringContent, $i, 1); + if (strlen($char) > 1) { + $filteredString .= $char; + } else { + $filteredString .= $filter($char); + } + } + + return $filteredString; + } + + protected static $libFeatureExists = ['feature']; + protected function libFeatureExists($args) + { + $string = $this->assertString($args[0], 'feature'); + $name = $this->compileStringContent($string); + + return $this->toBool( + \array_key_exists($name, $this->registeredFeatures) ? $this->registeredFeatures[$name] : false + ); + } + + protected static $libFunctionExists = ['name']; + protected function libFunctionExists($args) + { + $string = $this->assertString($args[0], 'name'); + $name = $this->compileStringContent($string); + + // user defined functions + if ($this->has(static::$namespaces['function'] . $name)) { + return self::$true; + } + + $name = $this->normalizeName($name); + + if (isset($this->userFunctions[$name])) { + return self::$true; + } + + // built-in functions + $f = $this->getBuiltinFunction($name); + + return $this->toBool(\is_callable($f)); + } + + protected static $libGlobalVariableExists = ['name']; + protected function libGlobalVariableExists($args) + { + $string = $this->assertString($args[0], 'name'); + $name = $this->compileStringContent($string); + + return $this->toBool($this->has($name, $this->rootEnv)); + } + + protected static $libMixinExists = ['name']; + protected function libMixinExists($args) + { + $string = $this->assertString($args[0], 'name'); + $name = $this->compileStringContent($string); + + return $this->toBool($this->has(static::$namespaces['mixin'] . $name)); + } + + protected static $libVariableExists = ['name']; + protected function libVariableExists($args) + { + $string = $this->assertString($args[0], 'name'); + $name = $this->compileStringContent($string); + + return $this->toBool($this->has($name)); + } + + protected static $libCounter = ['args...']; + /** + * Workaround IE7's content counter bug. + * + * @param array $args + * + * @return array + */ + protected function libCounter($args) + { + $list = array_map([$this, 'compileValue'], $args[0][2]); + + return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']]; + } + + protected static $libRandom = ['limit:null']; + protected function libRandom($args) + { + if (isset($args[0]) && $args[0] !== static::$null) { + $n = $this->assertInteger($args[0], 'limit'); + + if ($n < 1) { + throw new SassScriptException("\$limit: Must be greater than 0, was $n."); + } + + return new Number(mt_rand(1, $n), ''); + } + + $max = mt_getrandmax(); + return new Number(mt_rand(0, $max - 1) / $max, ''); + } + + protected static $libUniqueId = []; + protected function libUniqueId() + { + static $id; + + if (! isset($id)) { + $id = PHP_INT_SIZE === 4 + ? mt_rand(0, pow(36, 5)) . str_pad(mt_rand(0, pow(36, 5)) % 10000000, 7, '0', STR_PAD_LEFT) + : mt_rand(0, pow(36, 8)); + } + + $id += mt_rand(0, 10) + 1; + + return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]]; + } + + /** + * @param array|Number $value + * @param bool $force_enclosing_display + * + * @return array + */ + protected function inspectFormatValue($value, $force_enclosing_display = false) + { + if ($value === static::$null) { + $value = [Type::T_KEYWORD, 'null']; + } + + $stringValue = [$value]; + + if ($value instanceof Number) { + return [Type::T_STRING, '', $stringValue]; + } + + if ($value[0] === Type::T_LIST) { + if (end($value[2]) === static::$null) { + array_pop($value[2]); + $value[2][] = [Type::T_STRING, '', ['']]; + $force_enclosing_display = true; + } + + if ( + ! empty($value['enclosing']) && + ($force_enclosing_display || + ($value['enclosing'] === 'bracket') || + ! \count($value[2])) + ) { + $value['enclosing'] = 'forced_' . $value['enclosing']; + $force_enclosing_display = true; + } + + foreach ($value[2] as $k => $listelement) { + $value[2][$k] = $this->inspectFormatValue($listelement, $force_enclosing_display); + } + + $stringValue = [$value]; + } + + return [Type::T_STRING, '', $stringValue]; + } + + protected static $libInspect = ['value']; + protected function libInspect($args) + { + $value = $args[0]; + + return $this->inspectFormatValue($value); + } + + /** + * Preprocess selector args + * + * @param array $arg + * @param string|null $varname + * @param bool $allowParent + * + * @return array + */ + protected function getSelectorArg($arg, $varname = null, $allowParent = false) + { + static $parser = null; + + if (\is_null($parser)) { + $parser = $this->parserFactory(__METHOD__); + } + + if (! $this->checkSelectorArgType($arg)) { + $var_value = $this->compileValue($arg); + throw SassScriptException::forArgument("$var_value is not a valid selector: it must be a string, a list of strings, or a list of lists of strings", $varname); + } + + + if ($arg[0] === Type::T_STRING) { + $arg[1] = ''; + } + $arg = $this->compileValue($arg); + + $parsedSelector = []; + + if ($parser->parseSelector($arg, $parsedSelector, true)) { + $selector = $this->evalSelectors($parsedSelector); + $gluedSelector = $this->glueFunctionSelectors($selector); + + if (! $allowParent) { + foreach ($gluedSelector as $selector) { + foreach ($selector as $s) { + if (in_array(static::$selfSelector, $s)) { + throw SassScriptException::forArgument("Parent selectors aren't allowed here.", $varname); + } + } + } + } + + return $gluedSelector; + } + + throw SassScriptException::forArgument("expected more input, invalid selector.", $varname); + } + + /** + * Check variable type for getSelectorArg() function + * @param array $arg + * @param int $maxDepth + * @return bool + */ + protected function checkSelectorArgType($arg, $maxDepth = 2) + { + if ($arg[0] === Type::T_LIST && $maxDepth > 0) { + foreach ($arg[2] as $elt) { + if (! $this->checkSelectorArgType($elt, $maxDepth - 1)) { + return false; + } + } + return true; + } + if (!in_array($arg[0], [Type::T_STRING, Type::T_KEYWORD])) { + return false; + } + return true; + } + + /** + * Postprocess selector to output in right format + * + * @param array $selectors + * + * @return array + */ + protected function formatOutputSelector($selectors) + { + $selectors = $this->collapseSelectorsAsList($selectors); + + return $selectors; + } + + protected static $libIsSuperselector = ['super', 'sub']; + protected function libIsSuperselector($args) + { + list($super, $sub) = $args; + + $super = $this->getSelectorArg($super, 'super'); + $sub = $this->getSelectorArg($sub, 'sub'); + + return $this->toBool($this->isSuperSelector($super, $sub)); + } + + /** + * Test a $super selector again $sub + * + * @param array $super + * @param array $sub + * + * @return boolean + */ + protected function isSuperSelector($super, $sub) + { + // one and only one selector for each arg + if (! $super) { + throw $this->error('Invalid super selector for isSuperSelector()'); + } + + if (! $sub) { + throw $this->error('Invalid sub selector for isSuperSelector()'); + } + + if (count($sub) > 1) { + foreach ($sub as $s) { + if (! $this->isSuperSelector($super, [$s])) { + return false; + } + } + return true; + } + + if (count($super) > 1) { + foreach ($super as $s) { + if ($this->isSuperSelector([$s], $sub)) { + return true; + } + } + return false; + } + + $super = reset($super); + $sub = reset($sub); + + $i = 0; + $nextMustMatch = false; + + foreach ($super as $node) { + $compound = ''; + + array_walk_recursive( + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } + ); + + if ($this->isImmediateRelationshipCombinator($compound)) { + if ($node !== $sub[$i]) { + return false; + } + + $nextMustMatch = true; + $i++; + } else { + while ($i < \count($sub) && ! $this->isSuperPart($node, $sub[$i])) { + if ($nextMustMatch) { + return false; + } + + $i++; + } + + if ($i >= \count($sub)) { + return false; + } + + $nextMustMatch = false; + $i++; + } + } + + return true; + } + + /** + * Test a part of super selector again a part of sub selector + * + * @param array $superParts + * @param array $subParts + * + * @return boolean + */ + protected function isSuperPart($superParts, $subParts) + { + $i = 0; + + foreach ($superParts as $superPart) { + while ($i < \count($subParts) && $subParts[$i] !== $superPart) { + $i++; + } + + if ($i >= \count($subParts)) { + return false; + } + + $i++; + } + + return true; + } + + protected static $libSelectorAppend = ['selector...']; + protected function libSelectorAppend($args) + { + // get the selector... list + $args = reset($args); + $args = $args[2]; + + if (\count($args) < 1) { + throw $this->error('selector-append() needs at least 1 argument'); + } + + $selectors = []; + foreach ($args as $arg) { + $selectors[] = $this->getSelectorArg($arg, 'selector'); + } + + return $this->formatOutputSelector($this->selectorAppend($selectors)); + } + + /** + * Append parts of the last selector in the list to the previous, recursively + * + * @param array $selectors + * + * @return array + * + * @throws \ScssPhp\ScssPhp\Exception\CompilerException + */ + protected function selectorAppend($selectors) + { + $lastSelectors = array_pop($selectors); + + if (! $lastSelectors) { + throw $this->error('Invalid selector list in selector-append()'); + } + + while (\count($selectors)) { + $previousSelectors = array_pop($selectors); + + if (! $previousSelectors) { + throw $this->error('Invalid selector list in selector-append()'); + } + + // do the trick, happening $lastSelector to $previousSelector + $appended = []; + + foreach ($lastSelectors as $lastSelector) { + $previous = $previousSelectors; + + foreach ($lastSelector as $lastSelectorParts) { + foreach ($lastSelectorParts as $lastSelectorPart) { + foreach ($previous as $i => $previousSelector) { + foreach ($previousSelector as $j => $previousSelectorParts) { + $previous[$i][$j][] = $lastSelectorPart; + } + } + } + } + + foreach ($previous as $ps) { + $appended[] = $ps; + } + } + + $lastSelectors = $appended; + } + + return $lastSelectors; + } + + protected static $libSelectorExtend = [ + ['selector', 'extendee', 'extender'], + ['selectors', 'extendee', 'extender'] + ]; + protected function libSelectorExtend($args) + { + list($selectors, $extendee, $extender) = $args; + + $selectors = $this->getSelectorArg($selectors, 'selector'); + $extendee = $this->getSelectorArg($extendee, 'extendee'); + $extender = $this->getSelectorArg($extender, 'extender'); + + if (! $selectors || ! $extendee || ! $extender) { + throw $this->error('selector-extend() invalid arguments'); + } + + $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender); + + return $this->formatOutputSelector($extended); + } + + protected static $libSelectorReplace = [ + ['selector', 'original', 'replacement'], + ['selectors', 'original', 'replacement'] + ]; + protected function libSelectorReplace($args) + { + list($selectors, $original, $replacement) = $args; + + $selectors = $this->getSelectorArg($selectors, 'selector'); + $original = $this->getSelectorArg($original, 'original'); + $replacement = $this->getSelectorArg($replacement, 'replacement'); + + if (! $selectors || ! $original || ! $replacement) { + throw $this->error('selector-replace() invalid arguments'); + } + + $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true); + + return $this->formatOutputSelector($replaced); + } + + /** + * Extend/replace in selectors + * used by selector-extend and selector-replace that use the same logic + * + * @param array $selectors + * @param array $extendee + * @param array $extender + * @param boolean $replace + * + * @return array + */ + protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $replace = false) + { + $saveExtends = $this->extends; + $saveExtendsMap = $this->extendsMap; + + $this->extends = []; + $this->extendsMap = []; + + foreach ($extendee as $es) { + if (\count($es) !== 1) { + throw $this->error('Can\'t extend complex selector.'); + } + + // only use the first one + $this->pushExtends(reset($es), $extender, null); + } + + $extended = []; + + foreach ($selectors as $selector) { + if (! $replace) { + $extended[] = $selector; + } + + $n = \count($extended); + + $this->matchExtends($selector, $extended); + + // if didnt match, keep the original selector if we are in a replace operation + if ($replace && \count($extended) === $n) { + $extended[] = $selector; + } + } + + $this->extends = $saveExtends; + $this->extendsMap = $saveExtendsMap; + + return $extended; + } + + protected static $libSelectorNest = ['selector...']; + protected function libSelectorNest($args) + { + // get the selector... list + $args = reset($args); + $args = $args[2]; + + if (\count($args) < 1) { + throw $this->error('selector-nest() needs at least 1 argument'); + } + + $selectorsMap = []; + foreach ($args as $arg) { + $selectorsMap[] = $this->getSelectorArg($arg, 'selector', true); + } + + $envs = []; + + foreach ($selectorsMap as $selectors) { + $env = new Environment(); + $env->selectors = $selectors; + + $envs[] = $env; + } + + $envs = array_reverse($envs); + $env = $this->extractEnv($envs); + $outputSelectors = $this->multiplySelectors($env); + + return $this->formatOutputSelector($outputSelectors); + } + + protected static $libSelectorParse = [ + ['selector'], + ['selectors'] + ]; + protected function libSelectorParse($args) + { + $selectors = reset($args); + $selectors = $this->getSelectorArg($selectors, 'selector'); + + return $this->formatOutputSelector($selectors); + } + + protected static $libSelectorUnify = ['selectors1', 'selectors2']; + protected function libSelectorUnify($args) + { + list($selectors1, $selectors2) = $args; + + $selectors1 = $this->getSelectorArg($selectors1, 'selectors1'); + $selectors2 = $this->getSelectorArg($selectors2, 'selectors2'); + + if (! $selectors1 || ! $selectors2) { + throw $this->error('selector-unify() invalid arguments'); + } + + // only consider the first compound of each + $compound1 = reset($selectors1); + $compound2 = reset($selectors2); + + // unify them and that's it + $unified = $this->unifyCompoundSelectors($compound1, $compound2); + + return $this->formatOutputSelector($unified); + } + + /** + * The selector-unify magic as its best + * (at least works as expected on test cases) + * + * @param array $compound1 + * @param array $compound2 + * + * @return array + */ + protected function unifyCompoundSelectors($compound1, $compound2) + { + if (! \count($compound1)) { + return $compound2; + } + + if (! \count($compound2)) { + return $compound1; + } + + // check that last part are compatible + $lastPart1 = array_pop($compound1); + $lastPart2 = array_pop($compound2); + $last = $this->mergeParts($lastPart1, $lastPart2); + + if (! $last) { + return [[]]; + } + + $unifiedCompound = [$last]; + $unifiedSelectors = [$unifiedCompound]; + + // do the rest + while (\count($compound1) || \count($compound2)) { + $part1 = end($compound1); + $part2 = end($compound2); + + if ($part1 && ($match2 = $this->matchPartInCompound($part1, $compound2))) { + list($compound2, $part2, $after2) = $match2; + + if ($after2) { + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after2); + } + + $c = $this->mergeParts($part1, $part2); + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); + + $part1 = $part2 = null; + + array_pop($compound1); + } + + if ($part2 && ($match1 = $this->matchPartInCompound($part2, $compound1))) { + list($compound1, $part1, $after1) = $match1; + + if ($after1) { + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after1); + } + + $c = $this->mergeParts($part2, $part1); + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); + + $part1 = $part2 = null; + + array_pop($compound2); + } + + $new = []; + + if ($part1 && $part2) { + array_pop($compound1); + array_pop($compound2); + + $s = $this->prependSelectors($unifiedSelectors, [$part2]); + $new = array_merge($new, $this->prependSelectors($s, [$part1])); + $s = $this->prependSelectors($unifiedSelectors, [$part1]); + $new = array_merge($new, $this->prependSelectors($s, [$part2])); + } elseif ($part1) { + array_pop($compound1); + + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part1])); + } elseif ($part2) { + array_pop($compound2); + + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part2])); + } + + if ($new) { + $unifiedSelectors = $new; + } + } + + return $unifiedSelectors; + } + + /** + * Prepend each selector from $selectors with $parts + * + * @param array $selectors + * @param array $parts + * + * @return array + */ + protected function prependSelectors($selectors, $parts) + { + $new = []; + + foreach ($selectors as $compoundSelector) { + array_unshift($compoundSelector, $parts); + + $new[] = $compoundSelector; + } + + return $new; + } + + /** + * Try to find a matching part in a compound: + * - with same html tag name + * - with some class or id or something in common + * + * @param array $part + * @param array $compound + * + * @return array|false + */ + protected function matchPartInCompound($part, $compound) + { + $partTag = $this->findTagName($part); + $before = $compound; + $after = []; + + // try to find a match by tag name first + while (\count($before)) { + $p = array_pop($before); + + if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) { + return [$before, $p, $after]; + } + + $after[] = $p; + } + + // try again matching a non empty intersection and a compatible tagname + $before = $compound; + $after = []; + + while (\count($before)) { + $p = array_pop($before); + + if ($this->checkCompatibleTags($partTag, $this->findTagName($p))) { + if (\count(array_intersect($part, $p))) { + return [$before, $p, $after]; + } + } + + $after[] = $p; + } + + return false; + } + + /** + * Merge two part list taking care that + * - the html tag is coming first - if any + * - the :something are coming last + * + * @param array $parts1 + * @param array $parts2 + * + * @return array + */ + protected function mergeParts($parts1, $parts2) + { + $tag1 = $this->findTagName($parts1); + $tag2 = $this->findTagName($parts2); + $tag = $this->checkCompatibleTags($tag1, $tag2); + + // not compatible tags + if ($tag === false) { + return []; + } + + if ($tag) { + if ($tag1) { + $parts1 = array_diff($parts1, [$tag1]); + } + + if ($tag2) { + $parts2 = array_diff($parts2, [$tag2]); + } + } + + $mergedParts = array_merge($parts1, $parts2); + $mergedOrderedParts = []; + + foreach ($mergedParts as $part) { + if (strpos($part, ':') === 0) { + $mergedOrderedParts[] = $part; + } + } + + $mergedParts = array_diff($mergedParts, $mergedOrderedParts); + $mergedParts = array_merge($mergedParts, $mergedOrderedParts); + + if ($tag) { + array_unshift($mergedParts, $tag); + } + + return $mergedParts; + } + + /** + * Check the compatibility between two tag names: + * if both are defined they should be identical or one has to be '*' + * + * @param string $tag1 + * @param string $tag2 + * + * @return array|false + */ + protected function checkCompatibleTags($tag1, $tag2) + { + $tags = [$tag1, $tag2]; + $tags = array_unique($tags); + $tags = array_filter($tags); + + if (\count($tags) > 1) { + $tags = array_diff($tags, ['*']); + } + + // not compatible nodes + if (\count($tags) > 1) { + return false; + } + + return $tags; + } + + /** + * Find the html tag name in a selector parts list + * + * @param string[] $parts + * + * @return string + */ + protected function findTagName($parts) + { + foreach ($parts as $part) { + if (! preg_match('/^[\[.:#%_-]/', $part)) { + return $part; + } + } + + return ''; + } + + protected static $libSimpleSelectors = ['selector']; + protected function libSimpleSelectors($args) + { + $selector = reset($args); + $selector = $this->getSelectorArg($selector, 'selector'); + + // remove selectors list layer, keeping the first one + $selector = reset($selector); + + // remove parts list layer, keeping the first part + $part = reset($selector); + + $listParts = []; + + foreach ($part as $p) { + $listParts[] = [Type::T_STRING, '', [$p]]; + } + + return [Type::T_LIST, ',', $listParts]; + } + + protected static $libScssphpGlob = ['pattern']; + protected function libScssphpGlob($args) + { + @trigger_error(sprintf('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0. Register your own alternative through "%s::registerFunction', __CLASS__), E_USER_DEPRECATED); + + $this->logger->warn('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0.', true); + + $string = $this->assertString($args[0], 'pattern'); + $pattern = $this->compileStringContent($string); + $matches = glob($pattern); + $listParts = []; + + foreach ($matches as $match) { + if (! is_file($match)) { + continue; + } + + $listParts[] = [Type::T_STRING, '"', [$match]]; + } + + return [Type::T_LIST, ',', $listParts]; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Compiler/CachedResult.php b/plugins/admin/vendor/scssphp/scssphp/src/Compiler/CachedResult.php new file mode 100644 index 0000000..a662919 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Compiler/CachedResult.php @@ -0,0 +1,77 @@ + + */ + private $parsedFiles; + + /** + * @var array + * @phpstan-var list + */ + private $resolvedImports; + + /** + * @param CompilationResult $result + * @param array $parsedFiles + * @param array $resolvedImports + * + * @phpstan-param list $resolvedImports + */ + public function __construct(CompilationResult $result, array $parsedFiles, array $resolvedImports) + { + $this->result = $result; + $this->parsedFiles = $parsedFiles; + $this->resolvedImports = $resolvedImports; + } + + /** + * @return CompilationResult + */ + public function getResult() + { + return $this->result; + } + + /** + * @return array + */ + public function getParsedFiles() + { + return $this->parsedFiles; + } + + /** + * @return array + * + * @phpstan-return list + */ + public function getResolvedImports() + { + return $this->resolvedImports; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Compiler/Environment.php b/plugins/admin/vendor/scssphp/scssphp/src/Compiler/Environment.php new file mode 100644 index 0000000..306b15a --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Compiler/Environment.php @@ -0,0 +1,48 @@ + + * + * @internal + */ +class Environment +{ + /** + * @var \ScssPhp\ScssPhp\Block|null + */ + public $block; + + /** + * @var \ScssPhp\ScssPhp\Compiler\Environment|null + */ + public $parent; + + /** + * @var array + */ + public $store; + + /** + * @var array + */ + public $storeUnreduced; + + /** + * @var integer + */ + public $depth; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Exception/CompilerException.php b/plugins/admin/vendor/scssphp/scssphp/src/Exception/CompilerException.php new file mode 100644 index 0000000..0b00cf5 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Exception/CompilerException.php @@ -0,0 +1,24 @@ + + * + * @internal + */ +class CompilerException extends \Exception implements SassException +{ +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Exception/ParserException.php b/plugins/admin/vendor/scssphp/scssphp/src/Exception/ParserException.php new file mode 100644 index 0000000..00d77ec --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Exception/ParserException.php @@ -0,0 +1,50 @@ + + * + * @internal + */ +class ParserException extends \Exception implements SassException +{ + /** + * @var array + */ + private $sourcePosition; + + /** + * Get source position + * + * @api + */ + public function getSourcePosition() + { + return $this->sourcePosition; + } + + /** + * Set source position + * + * @api + * + * @param array $sourcePosition + */ + public function setSourcePosition($sourcePosition) + { + $this->sourcePosition = $sourcePosition; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Exception/RangeException.php b/plugins/admin/vendor/scssphp/scssphp/src/Exception/RangeException.php new file mode 100644 index 0000000..4be4dee --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Exception/RangeException.php @@ -0,0 +1,24 @@ + + * + * @internal + */ +class RangeException extends \Exception implements SassException +{ +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Exception/SassException.php b/plugins/admin/vendor/scssphp/scssphp/src/Exception/SassException.php new file mode 100644 index 0000000..9f62b3c --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Exception/SassException.php @@ -0,0 +1,7 @@ + + * + * @deprecated The Scssphp server should define its own exception instead. + */ +class ServerException extends \Exception implements SassException +{ +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter.php new file mode 100644 index 0000000..cc42ae8 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter.php @@ -0,0 +1,364 @@ + + * + * @internal + */ +abstract class Formatter +{ + /** + * @var integer + */ + public $indentLevel; + + /** + * @var string + */ + public $indentChar; + + /** + * @var string + */ + public $break; + + /** + * @var string + */ + public $open; + + /** + * @var string + */ + public $close; + + /** + * @var string + */ + public $tagSeparator; + + /** + * @var string + */ + public $assignSeparator; + + /** + * @var boolean + */ + public $keepSemicolons; + + /** + * @var \ScssPhp\ScssPhp\Formatter\OutputBlock + */ + protected $currentBlock; + + /** + * @var integer + */ + protected $currentLine; + + /** + * @var integer + */ + protected $currentColumn; + + /** + * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null + */ + protected $sourceMapGenerator; + + /** + * @var string + */ + protected $strippedSemicolon; + + /** + * Initialize formatter + * + * @api + */ + abstract public function __construct(); + + /** + * Return indentation (whitespace) + * + * @return string + */ + protected function indentStr() + { + return ''; + } + + /** + * Return property assignment + * + * @api + * + * @param string $name + * @param mixed $value + * + * @return string + */ + public function property($name, $value) + { + return rtrim($name) . $this->assignSeparator . $value . ';'; + } + + /** + * Return custom property assignment + * differs in that you have to keep spaces in the value as is + * + * @api + * + * @param string $name + * @param mixed $value + * + * @return string + */ + public function customProperty($name, $value) + { + return rtrim($name) . trim($this->assignSeparator) . $value . ';'; + } + + /** + * Output lines inside a block + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return void + */ + protected function blockLines(OutputBlock $block) + { + $inner = $this->indentStr(); + $glue = $this->break . $inner; + + $this->write($inner . implode($glue, $block->lines)); + + if (! empty($block->children)) { + $this->write($this->break); + } + } + + /** + * Output block selectors + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return void + */ + protected function blockSelectors(OutputBlock $block) + { + assert(! empty($block->selectors)); + + $inner = $this->indentStr(); + + $this->write($inner + . implode($this->tagSeparator, $block->selectors) + . $this->open . $this->break); + } + + /** + * Output block children + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return void + */ + protected function blockChildren(OutputBlock $block) + { + foreach ($block->children as $child) { + $this->block($child); + } + } + + /** + * Output non-empty block + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return void + */ + protected function block(OutputBlock $block) + { + if (empty($block->lines) && empty($block->children)) { + return; + } + + $this->currentBlock = $block; + + $pre = $this->indentStr(); + + if (! empty($block->selectors)) { + $this->blockSelectors($block); + + $this->indentLevel++; + } + + if (! empty($block->lines)) { + $this->blockLines($block); + } + + if (! empty($block->children)) { + $this->blockChildren($block); + } + + if (! empty($block->selectors)) { + $this->indentLevel--; + + if (! $this->keepSemicolons) { + $this->strippedSemicolon = ''; + } + + if (empty($block->children)) { + $this->write($this->break); + } + + $this->write($pre . $this->close . $this->break); + } + } + + /** + * Test and clean safely empty children + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return boolean + */ + protected function testEmptyChildren($block) + { + $isEmpty = empty($block->lines); + + if ($block->children) { + foreach ($block->children as $k => &$child) { + if (! $this->testEmptyChildren($child)) { + $isEmpty = false; + continue; + } + + if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) { + $child->children = []; + $child->selectors = null; + } + } + } + + return $isEmpty; + } + + /** + * Entry point to formatting a block + * + * @api + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree + * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator + * + * @return string + */ + public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null) + { + $this->sourceMapGenerator = null; + + if ($sourceMapGenerator) { + $this->currentLine = 1; + $this->currentColumn = 0; + $this->sourceMapGenerator = $sourceMapGenerator; + } + + $this->testEmptyChildren($block); + + ob_start(); + + $this->block($block); + + $out = ob_get_clean(); + + return $out; + } + + /** + * Output content + * + * @param string $str + * + * @return void + */ + protected function write($str) + { + if (! empty($this->strippedSemicolon)) { + echo $this->strippedSemicolon; + + $this->strippedSemicolon = ''; + } + + /* + * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator + * will be striped for real before a closing, otherwise displayed unchanged starting the next write + */ + if ( + ! $this->keepSemicolons && + $str && + (strpos($str, ';') !== false) && + (substr($str, -1) === ';') + ) { + $str = substr($str, 0, -1); + + $this->strippedSemicolon = ';'; + } + + if ($this->sourceMapGenerator) { + $lines = explode("\n", $str); + $lastLine = array_pop($lines); + + foreach ($lines as $line) { + // If the written line starts is empty, adding a mapping would add it for + // a non-existent column as we are at the end of the line + if ($line !== '') { + $this->sourceMapGenerator->addMapping( + $this->currentLine, + $this->currentColumn, + $this->currentBlock->sourceLine, + //columns from parser are off by one + $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0, + $this->currentBlock->sourceName + ); + } + + $this->currentLine++; + $this->currentColumn = 0; + } + + if ($lastLine !== '') { + $this->sourceMapGenerator->addMapping( + $this->currentLine, + $this->currentColumn, + $this->currentBlock->sourceLine, + //columns from parser are off by one + $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0, + $this->currentBlock->sourceName + ); + } + + $this->currentColumn += \strlen($lastLine); + } + + echo $str; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compact.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compact.php new file mode 100644 index 0000000..22f2268 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compact.php @@ -0,0 +1,52 @@ + + * + * @deprecated since 1.4.0. Use the Compressed formatter instead. + * + * @internal + */ +class Compact extends Formatter +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + @trigger_error('The Compact formatter is deprecated since 1.4.0. Use the Compressed formatter instead.', E_USER_DEPRECATED); + + $this->indentLevel = 0; + $this->indentChar = ''; + $this->break = ''; + $this->open = ' {'; + $this->close = "}\n\n"; + $this->tagSeparator = ','; + $this->assignSeparator = ':'; + $this->keepSemicolons = true; + } + + /** + * {@inheritdoc} + */ + public function indentStr() + { + return ' '; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compressed.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compressed.php new file mode 100644 index 0000000..de13c18 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Compressed.php @@ -0,0 +1,85 @@ + + * + * @internal + */ +class Compressed extends Formatter +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + $this->indentLevel = 0; + $this->indentChar = ' '; + $this->break = ''; + $this->open = '{'; + $this->close = '}'; + $this->tagSeparator = ','; + $this->assignSeparator = ':'; + $this->keepSemicolons = false; + } + + /** + * {@inheritdoc} + */ + public function blockLines(OutputBlock $block) + { + $inner = $this->indentStr(); + + $glue = $this->break . $inner; + + foreach ($block->lines as $index => $line) { + if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') { + unset($block->lines[$index]); + } elseif (substr($line, 0, 3) === '/*!') { + $block->lines[$index] = '/*' . substr($line, 3); + } + } + + $this->write($inner . implode($glue, $block->lines)); + + if (! empty($block->children)) { + $this->write($this->break); + } + } + + /** + * Output block selectors + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + */ + protected function blockSelectors(OutputBlock $block) + { + assert(! empty($block->selectors)); + + $inner = $this->indentStr(); + + $this->write( + $inner + . implode( + $this->tagSeparator, + str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors) + ) + . $this->open . $this->break + ); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Crunched.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Crunched.php new file mode 100644 index 0000000..2bc1e92 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Crunched.php @@ -0,0 +1,87 @@ + + * + * @deprecated since 1.4.0. Use the Compressed formatter instead. + * + * @internal + */ +class Crunched extends Formatter +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + @trigger_error('The Crunched formatter is deprecated since 1.4.0. Use the Compressed formatter instead.', E_USER_DEPRECATED); + + $this->indentLevel = 0; + $this->indentChar = ' '; + $this->break = ''; + $this->open = '{'; + $this->close = '}'; + $this->tagSeparator = ','; + $this->assignSeparator = ':'; + $this->keepSemicolons = false; + } + + /** + * {@inheritdoc} + */ + public function blockLines(OutputBlock $block) + { + $inner = $this->indentStr(); + + $glue = $this->break . $inner; + + foreach ($block->lines as $index => $line) { + if (substr($line, 0, 2) === '/*') { + unset($block->lines[$index]); + } + } + + $this->write($inner . implode($glue, $block->lines)); + + if (! empty($block->children)) { + $this->write($this->break); + } + } + + /** + * Output block selectors + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + */ + protected function blockSelectors(OutputBlock $block) + { + assert(! empty($block->selectors)); + + $inner = $this->indentStr(); + + $this->write( + $inner + . implode( + $this->tagSeparator, + str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors) + ) + . $this->open . $this->break + ); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Debug.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Debug.php new file mode 100644 index 0000000..b3f4422 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Debug.php @@ -0,0 +1,127 @@ + + * + * @deprecated since 1.4.0. + * + * @internal + */ +class Debug extends Formatter +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + @trigger_error('The Debug formatter is deprecated since 1.4.0.', E_USER_DEPRECATED); + + $this->indentLevel = 0; + $this->indentChar = ''; + $this->break = "\n"; + $this->open = ' {'; + $this->close = ' }'; + $this->tagSeparator = ', '; + $this->assignSeparator = ': '; + $this->keepSemicolons = true; + } + + /** + * {@inheritdoc} + */ + protected function indentStr() + { + return str_repeat(' ', $this->indentLevel); + } + + /** + * {@inheritdoc} + */ + protected function blockLines(OutputBlock $block) + { + $indent = $this->indentStr(); + + if (empty($block->lines)) { + $this->write("{$indent}block->lines: []\n"); + + return; + } + + foreach ($block->lines as $index => $line) { + $this->write("{$indent}block->lines[{$index}]: $line\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function blockSelectors(OutputBlock $block) + { + $indent = $this->indentStr(); + + if (empty($block->selectors)) { + $this->write("{$indent}block->selectors: []\n"); + + return; + } + + foreach ($block->selectors as $index => $selector) { + $this->write("{$indent}block->selectors[{$index}]: $selector\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function blockChildren(OutputBlock $block) + { + $indent = $this->indentStr(); + + if (empty($block->children)) { + $this->write("{$indent}block->children: []\n"); + + return; + } + + $this->indentLevel++; + + foreach ($block->children as $i => $child) { + $this->block($child); + } + + $this->indentLevel--; + } + + /** + * {@inheritdoc} + */ + protected function block(OutputBlock $block) + { + $indent = $this->indentStr(); + + $this->write("{$indent}block->type: {$block->type}\n" . + "{$indent}block->depth: {$block->depth}\n"); + + $this->currentBlock = $block; + + $this->blockSelectors($block); + $this->blockLines($block); + $this->blockChildren($block); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Expanded.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Expanded.php new file mode 100644 index 0000000..a280416 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Expanded.php @@ -0,0 +1,70 @@ + + * + * @internal + */ +class Expanded extends Formatter +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + $this->indentLevel = 0; + $this->indentChar = ' '; + $this->break = "\n"; + $this->open = ' {'; + $this->close = '}'; + $this->tagSeparator = ', '; + $this->assignSeparator = ': '; + $this->keepSemicolons = true; + } + + /** + * {@inheritdoc} + */ + protected function indentStr() + { + return str_repeat($this->indentChar, $this->indentLevel); + } + + /** + * {@inheritdoc} + */ + protected function blockLines(OutputBlock $block) + { + $inner = $this->indentStr(); + + $glue = $this->break . $inner; + + foreach ($block->lines as $index => $line) { + if (substr($line, 0, 2) === '/*') { + $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + } + } + + $this->write($inner . implode($glue, $block->lines)); + + if (empty($block->selectors) || ! empty($block->children)) { + $this->write($this->break); + } + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Nested.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Nested.php new file mode 100644 index 0000000..9e72956 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/Nested.php @@ -0,0 +1,236 @@ + + * + * @deprecated since 1.4.0. Use the Expanded formatter instead. + * + * @internal + */ +class Nested extends Formatter +{ + /** + * @var integer + */ + private $depth; + + /** + * {@inheritdoc} + */ + public function __construct() + { + @trigger_error('The Nested formatter is deprecated since 1.4.0. Use the Expanded formatter instead.', E_USER_DEPRECATED); + + $this->indentLevel = 0; + $this->indentChar = ' '; + $this->break = "\n"; + $this->open = ' {'; + $this->close = ' }'; + $this->tagSeparator = ', '; + $this->assignSeparator = ': '; + $this->keepSemicolons = true; + } + + /** + * {@inheritdoc} + */ + protected function indentStr() + { + $n = $this->depth - 1; + + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + /** + * {@inheritdoc} + */ + protected function blockLines(OutputBlock $block) + { + $inner = $this->indentStr(); + $glue = $this->break . $inner; + + foreach ($block->lines as $index => $line) { + if (substr($line, 0, 2) === '/*') { + $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + } + } + + $this->write($inner . implode($glue, $block->lines)); + } + + /** + * {@inheritdoc} + */ + protected function block(OutputBlock $block) + { + static $depths; + static $downLevel; + static $closeBlock; + static $previousEmpty; + static $previousHasSelector; + + if ($block->type === 'root') { + $depths = [ 0 ]; + $downLevel = ''; + $closeBlock = ''; + $this->depth = 0; + $previousEmpty = false; + $previousHasSelector = false; + } + + $isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]); + $isSupport = ($block->type === Type::T_DIRECTIVE + && $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false); + + while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) { + array_pop($depths); + $this->depth--; + + if ( + ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) && + (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector) + ) { + $downLevel = $this->break; + } + + if (empty($block->lines) && empty($block->children)) { + $previousEmpty = true; + } + } + + if (empty($block->lines) && empty($block->children)) { + return; + } + + $this->currentBlock = $block; + + if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) { + if ($block->depth > end($depths)) { + if (! $previousEmpty || $this->depth < 1) { + $this->depth++; + + $depths[] = $block->depth; + } else { + // keep the current depth unchanged but take the block depth as a new reference for following blocks + array_pop($depths); + + $depths[] = $block->depth; + } + } + } + + $previousEmpty = ($block->type === Type::T_COMMENT); + $previousHasSelector = false; + + if (! empty($block->selectors)) { + if ($closeBlock) { + $this->write($closeBlock); + $closeBlock = ''; + } + + if ($downLevel) { + $this->write($downLevel); + $downLevel = ''; + } + + $this->blockSelectors($block); + + $this->indentLevel++; + } + + if (! empty($block->lines)) { + if ($closeBlock) { + $this->write($closeBlock); + $closeBlock = ''; + } + + if ($downLevel) { + $this->write($downLevel); + $downLevel = ''; + } + + $this->blockLines($block); + + $closeBlock = $this->break; + } + + if (! empty($block->children)) { + if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) { + array_pop($depths); + + $this->depth--; + $this->blockChildren($block); + $this->depth++; + + $depths[] = $block->depth; + } else { + $this->blockChildren($block); + } + } + + // reclear to not be spoiled by children if T_DIRECTIVE + if ($block->type === Type::T_DIRECTIVE) { + $previousHasSelector = false; + } + + if (! empty($block->selectors)) { + $this->indentLevel--; + + if (! $this->keepSemicolons) { + $this->strippedSemicolon = ''; + } + + $this->write($this->close); + + $closeBlock = $this->break; + + if ($this->depth > 1 && ! empty($block->children)) { + array_pop($depths); + $this->depth--; + } + + if (! $isMediaOrDirective) { + $previousHasSelector = true; + } + } + + if ($block->type === 'root') { + $this->write($this->break); + } + } + + /** + * Block has flat child + * + * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block + * + * @return boolean + */ + private function hasFlatChild($block) + { + foreach ($block->children as $child) { + if (empty($child->selectors)) { + return true; + } + } + + return false; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php new file mode 100644 index 0000000..88deb2d --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php @@ -0,0 +1,68 @@ + + * + * @internal + */ +class OutputBlock +{ + /** + * @var string + */ + public $type; + + /** + * @var integer + */ + public $depth; + + /** + * @var array|null + */ + public $selectors; + + /** + * @var string[] + */ + public $lines; + + /** + * @var OutputBlock[] + */ + public $children; + + /** + * @var OutputBlock|null + */ + public $parent; + + /** + * @var string|null + */ + public $sourceName; + + /** + * @var integer|null + */ + public $sourceLine; + + /** + * @var integer|null + */ + public $sourceColumn; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Logger/LoggerInterface.php b/plugins/admin/vendor/scssphp/scssphp/src/Logger/LoggerInterface.php new file mode 100644 index 0000000..7c0a2f7 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Logger/LoggerInterface.php @@ -0,0 +1,48 @@ +stream = $stream; + $this->closeOnDestruct = $closeOnDestruct; + } + + /** + * @internal + */ + public function __destruct() + { + if ($this->closeOnDestruct) { + fclose($this->stream); + } + } + + /** + * @inheritDoc + */ + public function warn($message, $deprecation = false) + { + $prefix = ($deprecation ? 'DEPRECATION ' : '') . 'WARNING: '; + + fwrite($this->stream, $prefix . $message . "\n\n"); + } + + /** + * @inheritDoc + */ + public function debug($message) + { + fwrite($this->stream, $message . "\n"); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Node.php b/plugins/admin/vendor/scssphp/scssphp/src/Node.php new file mode 100644 index 0000000..5301937 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Node.php @@ -0,0 +1,43 @@ + + * + * @internal + */ +abstract class Node +{ + /** + * @var string + */ + public $type; + + /** + * @var integer + */ + public $sourceIndex; + + /** + * @var int|null + */ + public $sourceLine; + + /** + * @var int|null + */ + public $sourceColumn; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Node/Number.php b/plugins/admin/vendor/scssphp/scssphp/src/Node/Number.php new file mode 100644 index 0000000..b326906 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Node/Number.php @@ -0,0 +1,804 @@ + + * + * @template-implements \ArrayAccess + */ +class Number extends Node implements \ArrayAccess +{ + const PRECISION = 10; + + /** + * @var integer + * @deprecated use {Number::PRECISION} instead to read the precision. Configuring it is not supported anymore. + */ + public static $precision = self::PRECISION; + + /** + * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/ + * + * @var array + * @phpstan-var array> + */ + protected static $unitTable = [ + 'in' => [ + 'in' => 1, + 'pc' => 6, + 'pt' => 72, + 'px' => 96, + 'cm' => 2.54, + 'mm' => 25.4, + 'q' => 101.6, + ], + 'turn' => [ + 'deg' => 360, + 'grad' => 400, + 'rad' => 6.28318530717958647692528676, // 2 * M_PI + 'turn' => 1, + ], + 's' => [ + 's' => 1, + 'ms' => 1000, + ], + 'Hz' => [ + 'Hz' => 1, + 'kHz' => 0.001, + ], + 'dpi' => [ + 'dpi' => 1, + 'dpcm' => 1 / 2.54, + 'dppx' => 1 / 96, + ], + ]; + + /** + * @var integer|float + */ + private $dimension; + + /** + * @var string[] + * @phpstan-var list + */ + private $numeratorUnits; + + /** + * @var string[] + * @phpstan-var list + */ + private $denominatorUnits; + + /** + * Initialize number + * + * @param integer|float $dimension + * @param string[]|string $numeratorUnits + * @param string[] $denominatorUnits + * + * @phpstan-param list|string $numeratorUnits + * @phpstan-param list $denominatorUnits + */ + public function __construct($dimension, $numeratorUnits, array $denominatorUnits = []) + { + if (is_string($numeratorUnits)) { + $numeratorUnits = $numeratorUnits ? [$numeratorUnits] : []; + } elseif (isset($numeratorUnits['numerator_units'], $numeratorUnits['denominator_units'])) { + // TODO get rid of this once `$number[2]` is not used anymore + $denominatorUnits = $numeratorUnits['denominator_units']; + $numeratorUnits = $numeratorUnits['numerator_units']; + } + + $this->dimension = $dimension; + $this->numeratorUnits = $numeratorUnits; + $this->denominatorUnits = $denominatorUnits; + } + + /** + * @return float|int + */ + public function getDimension() + { + return $this->dimension; + } + + /** + * @return string[] + */ + public function getNumeratorUnits() + { + return $this->numeratorUnits; + } + + /** + * @return string[] + */ + public function getDenominatorUnits() + { + return $this->denominatorUnits; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + if ($offset === -3) { + return ! \is_null($this->sourceColumn); + } + + if ($offset === -2) { + return ! \is_null($this->sourceLine); + } + + if ( + $offset === -1 || + $offset === 0 || + $offset === 1 || + $offset === 2 + ) { + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + switch ($offset) { + case -3: + return $this->sourceColumn; + + case -2: + return $this->sourceLine; + + case -1: + return $this->sourceIndex; + + case 0: + return Type::T_NUMBER; + + case 1: + return $this->dimension; + + case 2: + return array('numerator_units' => $this->numeratorUnits, 'denominator_units' => $this->denominatorUnits); + } + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + throw new \BadMethodCallException('Number is immutable'); + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + throw new \BadMethodCallException('Number is immutable'); + } + + /** + * Returns true if the number is unitless + * + * @return boolean + */ + public function unitless() + { + return \count($this->numeratorUnits) === 0 && \count($this->denominatorUnits) === 0; + } + + /** + * Checks whether the number has exactly this unit + * + * @param string $unit + * + * @return bool + */ + public function hasUnit($unit) + { + return \count($this->numeratorUnits) === 1 && \count($this->denominatorUnits) === 0 && $this->numeratorUnits[0] === $unit; + } + + /** + * Returns unit(s) as the product of numerator units divided by the product of denominator units + * + * @return string + */ + public function unitStr() + { + if ($this->unitless()) { + return ''; + } + + return self::getUnitString($this->numeratorUnits, $this->denominatorUnits); + } + + /** + * @param float|int $min + * @param float|int $max + * @param string|null $name + * + * @return float|int + * @throws SassScriptException + */ + public function valueInRange($min, $max, $name = null) + { + try { + return Util::checkRange('', new Range($min, $max), $this); + } catch (RangeException $e) { + throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s', $this, $min, $this->unitStr(), $max), $name); + } + } + + /** + * @param string|null $varName + * + * @return void + */ + public function assertNoUnits($varName = null) + { + if ($this->unitless()) { + return; + } + + throw SassScriptException::forArgument(sprintf('Expected %s to have no units.', $this), $varName); + } + + /** + * @param string $unit + * @param string|null $varName + * + * @return void + */ + public function assertUnit($unit, $varName = null) + { + if ($this->hasUnit($unit)) { + return; + } + + throw SassScriptException::forArgument(sprintf('Expected %s to have unit "%s".', $this, $unit), $varName); + } + + /** + * @param Number $other + * + * @return void + */ + public function assertSameUnitOrUnitless(Number $other) + { + if ($other->unitless()) { + return; + } + + if ($this->numeratorUnits === $other->numeratorUnits && $this->denominatorUnits === $other->denominatorUnits) { + return; + } + + throw new SassScriptException(sprintf( + 'Incompatible units %s and %s.', + self::getUnitString($this->numeratorUnits, $this->denominatorUnits), + self::getUnitString($other->numeratorUnits, $other->denominatorUnits) + )); + } + + /** + * Returns a copy of this number, converted to the units represented by $newNumeratorUnits and $newDenominatorUnits. + * + * This does not throw an error if this number is unitless and + * $newNumeratorUnits/$newDenominatorUnits are not empty, or vice versa. Instead, + * it treats all unitless numbers as convertible to and from all units without + * changing the value. + * + * @param string[] $newNumeratorUnits + * @param string[] $newDenominatorUnits + * + * @return Number + * + * @phpstan-param list $newNumeratorUnits + * @phpstan-param list $newDenominatorUnits + * + * @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits + */ + public function coerce(array $newNumeratorUnits, array $newDenominatorUnits) + { + return new Number($this->valueInUnits($newNumeratorUnits, $newDenominatorUnits), $newNumeratorUnits, $newDenominatorUnits); + } + + /** + * @param Number $other + * + * @return bool + */ + public function isComparableTo(Number $other) + { + if ($this->unitless() || $other->unitless()) { + return true; + } + + try { + $this->greaterThan($other); + return true; + } catch (SassScriptException $e) { + return false; + } + } + + /** + * @param Number $other + * + * @return bool + */ + public function lessThan(Number $other) + { + return $this->coerceUnits($other, function ($num1, $num2) { + return $num1 < $num2; + }); + } + + /** + * @param Number $other + * + * @return bool + */ + public function lessThanOrEqual(Number $other) + { + return $this->coerceUnits($other, function ($num1, $num2) { + return $num1 <= $num2; + }); + } + + /** + * @param Number $other + * + * @return bool + */ + public function greaterThan(Number $other) + { + return $this->coerceUnits($other, function ($num1, $num2) { + return $num1 > $num2; + }); + } + + /** + * @param Number $other + * + * @return bool + */ + public function greaterThanOrEqual(Number $other) + { + return $this->coerceUnits($other, function ($num1, $num2) { + return $num1 >= $num2; + }); + } + + /** + * @param Number $other + * + * @return Number + */ + public function plus(Number $other) + { + return $this->coerceNumber($other, function ($num1, $num2) { + return $num1 + $num2; + }); + } + + /** + * @param Number $other + * + * @return Number + */ + public function minus(Number $other) + { + return $this->coerceNumber($other, function ($num1, $num2) { + return $num1 - $num2; + }); + } + + /** + * @return Number + */ + public function unaryMinus() + { + return new Number(-$this->dimension, $this->numeratorUnits, $this->denominatorUnits); + } + + /** + * @param Number $other + * + * @return Number + */ + public function modulo(Number $other) + { + return $this->coerceNumber($other, function ($num1, $num2) { + if ($num2 == 0) { + return NAN; + } + + $result = fmod($num1, $num2); + + if ($result == 0) { + return 0; + } + + if ($num2 < 0 xor $num1 < 0) { + $result += $num2; + } + + return $result; + }); + } + + /** + * @param Number $other + * + * @return Number + */ + public function times(Number $other) + { + return $this->multiplyUnits($this->dimension * $other->dimension, $this->numeratorUnits, $this->denominatorUnits, $other->numeratorUnits, $other->denominatorUnits); + } + + /** + * @param Number $other + * + * @return Number + */ + public function dividedBy(Number $other) + { + if ($other->dimension == 0) { + if ($this->dimension == 0) { + $value = NAN; + } elseif ($this->dimension > 0) { + $value = INF; + } else { + $value = -INF; + } + } else { + $value = $this->dimension / $other->dimension; + } + + return $this->multiplyUnits($value, $this->numeratorUnits, $this->denominatorUnits, $other->denominatorUnits, $other->numeratorUnits); + } + + /** + * @param Number $other + * + * @return bool + */ + public function equals(Number $other) + { + // Unitless numbers are convertable to unit numbers, but not equal, so we special-case unitless here. + if ($this->unitless() !== $other->unitless()) { + return false; + } + + // In Sass, neither NaN nor Infinity are equal to themselves, while PHP defines INF==INF + if (is_nan($this->dimension) || is_nan($other->dimension) || !is_finite($this->dimension) || !is_finite($other->dimension)) { + return false; + } + + if ($this->unitless()) { + return round($this->dimension, self::PRECISION) == round($other->dimension, self::PRECISION); + } + + try { + return $this->coerceUnits($other, function ($num1, $num2) { + return round($num1,self::PRECISION) == round($num2, self::PRECISION); + }); + } catch (SassScriptException $e) { + return false; + } + } + + /** + * Output number + * + * @param \ScssPhp\ScssPhp\Compiler $compiler + * + * @return string + */ + public function output(Compiler $compiler = null) + { + $dimension = round($this->dimension, self::PRECISION); + + if (is_nan($dimension)) { + return 'NaN'; + } + + if ($dimension === INF) { + return 'Infinity'; + } + + if ($dimension === -INF) { + return '-Infinity'; + } + + if ($compiler) { + $unit = $this->unitStr(); + } elseif (isset($this->numeratorUnits[0])) { + $unit = $this->numeratorUnits[0]; + } else { + $unit = ''; + } + + $dimension = number_format($dimension, self::PRECISION, '.', ''); + + return rtrim(rtrim($dimension, '0'), '.') . $unit; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->output(); + } + + /** + * @param Number $other + * @param callable $operation + * + * @return Number + * + * @phpstan-param callable(int|float, int|float): (int|float) $operation + */ + private function coerceNumber(Number $other, $operation) + { + $result = $this->coerceUnits($other, $operation); + + if (!$this->unitless()) { + return new Number($result, $this->numeratorUnits, $this->denominatorUnits); + } + + return new Number($result, $other->numeratorUnits, $other->denominatorUnits); + } + + /** + * @param Number $other + * @param callable $operation + * + * @return mixed + * + * @phpstan-template T + * @phpstan-param callable(int|float, int|float): T $operation + * @phpstan-return T + */ + private function coerceUnits(Number $other, $operation) + { + if (!$this->unitless()) { + $num1 = $this->dimension; + $num2 = $other->valueInUnits($this->numeratorUnits, $this->denominatorUnits); + } else { + $num1 = $this->valueInUnits($other->numeratorUnits, $other->denominatorUnits); + $num2 = $other->dimension; + } + + return \call_user_func($operation, $num1, $num2); + } + + /** + * @param string[] $numeratorUnits + * @param string[] $denominatorUnits + * + * @return int|float + * + * @phpstan-param list $numeratorUnits + * @phpstan-param list $denominatorUnits + * + * @throws SassScriptException if this number's units are not compatible with $numeratorUnits and $denominatorUnits + */ + private function valueInUnits(array $numeratorUnits, array $denominatorUnits) + { + if ( + $this->unitless() + || (\count($numeratorUnits) === 0 && \count($denominatorUnits) === 0) + || ($this->numeratorUnits === $numeratorUnits && $this->denominatorUnits === $denominatorUnits) + ) { + return $this->dimension; + } + + $value = $this->dimension; + $oldNumerators = $this->numeratorUnits; + + foreach ($numeratorUnits as $newNumerator) { + foreach ($oldNumerators as $key => $oldNumerator) { + $conversionFactor = self::getConversionFactor($newNumerator, $oldNumerator); + + if (\is_null($conversionFactor)) { + continue; + } + + $value *= $conversionFactor; + unset($oldNumerators[$key]); + continue 2; + } + + throw new SassScriptException(sprintf( + 'Incompatible units %s and %s.', + self::getUnitString($this->numeratorUnits, $this->denominatorUnits), + self::getUnitString($numeratorUnits, $denominatorUnits) + )); + } + + $oldDenominators = $this->denominatorUnits; + + foreach ($denominatorUnits as $newDenominator) { + foreach ($oldDenominators as $key => $oldDenominator) { + $conversionFactor = self::getConversionFactor($newDenominator, $oldDenominator); + + if (\is_null($conversionFactor)) { + continue; + } + + $value /= $conversionFactor; + unset($oldDenominators[$key]); + continue 2; + } + + throw new SassScriptException(sprintf( + 'Incompatible units %s and %s.', + self::getUnitString($this->numeratorUnits, $this->denominatorUnits), + self::getUnitString($numeratorUnits, $denominatorUnits) + )); + } + + if (\count($oldNumerators) || \count($oldDenominators)) { + throw new SassScriptException(sprintf( + 'Incompatible units %s and %s.', + self::getUnitString($this->numeratorUnits, $this->denominatorUnits), + self::getUnitString($numeratorUnits, $denominatorUnits) + )); + } + + return $value; + } + + /** + * @param int|float $value + * @param string[] $numerators1 + * @param string[] $denominators1 + * @param string[] $numerators2 + * @param string[] $denominators2 + * + * @return Number + * + * @phpstan-param list $numerators1 + * @phpstan-param list $denominators1 + * @phpstan-param list $numerators2 + * @phpstan-param list $denominators2 + */ + private function multiplyUnits($value, array $numerators1, array $denominators1, array $numerators2, array $denominators2) + { + $newNumerators = array(); + + foreach ($numerators1 as $numerator) { + foreach ($denominators2 as $key => $denominator) { + $conversionFactor = self::getConversionFactor($numerator, $denominator); + + if (\is_null($conversionFactor)) { + continue; + } + + $value /= $conversionFactor; + unset($denominators2[$key]); + continue 2; + } + + $newNumerators[] = $numerator; + } + + foreach ($numerators2 as $numerator) { + foreach ($denominators1 as $key => $denominator) { + $conversionFactor = self::getConversionFactor($numerator, $denominator); + + if (\is_null($conversionFactor)) { + continue; + } + + $value /= $conversionFactor; + unset($denominators1[$key]); + continue 2; + } + + $newNumerators[] = $numerator; + } + + $newDenominators = array_values(array_merge($denominators1, $denominators2)); + + return new Number($value, $newNumerators, $newDenominators); + } + + /** + * Returns the number of [unit1]s per [unit2]. + * + * Equivalently, `1unit1 * conversionFactor(unit1, unit2) = 1unit2`. + * + * @param string $unit1 + * @param string $unit2 + * + * @return float|int|null + */ + private static function getConversionFactor($unit1, $unit2) + { + if ($unit1 === $unit2) { + return 1; + } + + foreach (static::$unitTable as $unitVariants) { + if (isset($unitVariants[$unit1]) && isset($unitVariants[$unit2])) { + return $unitVariants[$unit1] / $unitVariants[$unit2]; + } + } + + return null; + } + + /** + * Returns unit(s) as the product of numerator units divided by the product of denominator units + * + * @param string[] $numerators + * @param string[] $denominators + * + * @phpstan-param list $numerators + * @phpstan-param list $denominators + * + * @return string + */ + private static function getUnitString(array $numerators, array $denominators) + { + if (!\count($numerators)) { + if (\count($denominators) === 0) { + return 'no units'; + } + + if (\count($denominators) === 1) { + return $denominators[0] . '^-1'; + } + + return '(' . implode('*', $denominators) . ')^-1'; + } + + return implode('*', $numerators) . (\count($denominators) ? '/' . implode('*', $denominators) : ''); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/OutputStyle.php b/plugins/admin/vendor/scssphp/scssphp/src/OutputStyle.php new file mode 100644 index 0000000..c284639 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/OutputStyle.php @@ -0,0 +1,9 @@ + + * + * @internal + */ +class Parser +{ + const SOURCE_INDEX = -1; + const SOURCE_LINE = -2; + const SOURCE_COLUMN = -3; + + /** + * @var array + */ + protected static $precedence = [ + '=' => 0, + 'or' => 1, + 'and' => 2, + '==' => 3, + '!=' => 3, + '<=' => 4, + '>=' => 4, + '<' => 4, + '>' => 4, + '+' => 5, + '-' => 5, + '*' => 6, + '/' => 6, + '%' => 6, + ]; + + /** + * @var string + */ + protected static $commentPattern; + /** + * @var string + */ + protected static $operatorPattern; + /** + * @var string + */ + protected static $whitePattern; + + /** + * @var Cache|null + */ + protected $cache; + + private $sourceName; + private $sourceIndex; + /** + * @var array + */ + private $sourcePositions; + /** + * @var array|null + */ + private $charset; + /** + * The current offset in the buffer + * + * @var int + */ + private $count; + /** + * @var Block|null + */ + private $env; + /** + * @var bool + */ + private $inParens; + /** + * @var bool + */ + private $eatWhiteDefault; + /** + * @var bool + */ + private $discardComments; + private $allowVars; + /** + * @var string + */ + private $buffer; + private $utf8; + /** + * @var string|null + */ + private $encoding; + private $patternModifiers; + private $commentsSeen; + + private $cssOnly; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * Constructor + * + * @api + * + * @param string|null $sourceName + * @param integer $sourceIndex + * @param string|null $encoding + * @param Cache|null $cache + * @param bool $cssOnly + * @param LoggerInterface|null $logger + */ + public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', Cache $cache = null, $cssOnly = false, LoggerInterface $logger = null) + { + $this->sourceName = $sourceName ?: '(stdin)'; + $this->sourceIndex = $sourceIndex; + $this->charset = null; + $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8'; + $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais'; + $this->commentsSeen = []; + $this->commentsSeen = []; + $this->allowVars = true; + $this->cssOnly = $cssOnly; + $this->logger = $logger ?: new QuietLogger(); + + if (empty(static::$operatorPattern)) { + static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=?|and|or)'; + + $commentSingle = '\/\/'; + $commentMultiLeft = '\/\*'; + $commentMultiRight = '\*\/'; + + static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight; + static::$whitePattern = $this->utf8 + ? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS' + : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS'; + } + + $this->cache = $cache; + } + + /** + * Get source file name + * + * @api + * + * @return string + */ + public function getSourceName() + { + return $this->sourceName; + } + + /** + * Throw parser error + * + * @api + * + * @param string $msg + * + * @phpstan-return never-return + * + * @throws ParserException + * + * @deprecated use "parseError" and throw the exception in the caller instead. + */ + public function throwParseError($msg = 'parse error') + { + @trigger_error( + 'The method "throwParseError" is deprecated. Use "parseError" and throw the exception in the caller instead', + E_USER_DEPRECATED + ); + + throw $this->parseError($msg); + } + + /** + * Creates a parser error + * + * @api + * + * @param string $msg + * + * @return ParserException + */ + public function parseError($msg = 'parse error') + { + list($line, $column) = $this->getSourcePosition($this->count); + + $loc = empty($this->sourceName) + ? "line: $line, column: $column" + : "$this->sourceName on line $line, at column $column"; + + if ($this->peek('(.*?)(\n|$)', $m, $this->count)) { + $this->restoreEncoding(); + + $e = new ParserException("$msg: failed at `$m[1]` $loc"); + $e->setSourcePosition([$this->sourceName, $line, $column]); + + return $e; + } + + $this->restoreEncoding(); + + $e = new ParserException("$msg: $loc"); + $e->setSourcePosition([$this->sourceName, $line, $column]); + + return $e; + } + + /** + * Parser buffer + * + * @api + * + * @param string $buffer + * + * @return Block + */ + public function parse($buffer) + { + if ($this->cache) { + $cacheKey = $this->sourceName . ':' . md5($buffer); + $parseOptions = [ + 'charset' => $this->charset, + 'utf8' => $this->utf8, + ]; + $v = $this->cache->getCache('parse', $cacheKey, $parseOptions); + + if (! \is_null($v)) { + return $v; + } + } + + // strip BOM (byte order marker) + if (substr($buffer, 0, 3) === "\xef\xbb\xbf") { + $buffer = substr($buffer, 3); + } + + $this->buffer = rtrim($buffer, "\x00..\x1f"); + $this->count = 0; + $this->env = null; + $this->inParens = false; + $this->eatWhiteDefault = true; + + $this->saveEncoding(); + $this->extractLineNumbers($buffer); + + $this->pushBlock(null); // root block + $this->whitespace(); + $this->pushBlock(null); + $this->popBlock(); + + while ($this->parseChunk()) { + ; + } + + if ($this->count !== \strlen($this->buffer)) { + throw $this->parseError(); + } + + if (! empty($this->env->parent)) { + throw $this->parseError('unclosed block'); + } + + if ($this->charset) { + array_unshift($this->env->children, $this->charset); + } + + $this->restoreEncoding(); + + if ($this->cache) { + $this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions); + } + + return $this->env; + } + + /** + * Parse a value or value list + * + * @api + * + * @param string $buffer + * @param string|array $out + * + * @return boolean + */ + public function parseValue($buffer, &$out) + { + $this->count = 0; + $this->env = null; + $this->inParens = false; + $this->eatWhiteDefault = true; + $this->buffer = (string) $buffer; + + $this->saveEncoding(); + $this->extractLineNumbers($this->buffer); + + $list = $this->valueList($out); + + $this->restoreEncoding(); + + return $list; + } + + /** + * Parse a selector or selector list + * + * @api + * + * @param string $buffer + * @param string|array $out + * @param bool $shouldValidate + * + * @return boolean + */ + public function parseSelector($buffer, &$out, $shouldValidate = true) + { + $this->count = 0; + $this->env = null; + $this->inParens = false; + $this->eatWhiteDefault = true; + $this->buffer = (string) $buffer; + + $this->saveEncoding(); + $this->extractLineNumbers($this->buffer); + + // discard space/comments at the start + $this->discardComments = true; + $this->whitespace(); + $this->discardComments = false; + + $selector = $this->selectors($out); + + $this->restoreEncoding(); + + if ($shouldValidate && $this->count !== strlen($buffer)) { + throw $this->parseError("`" . substr($buffer, $this->count) . "` is not a valid Selector in `$buffer`"); + } + + return $selector; + } + + /** + * Parse a media Query + * + * @api + * + * @param string $buffer + * @param string|array $out + * + * @return boolean + */ + public function parseMediaQueryList($buffer, &$out) + { + $this->count = 0; + $this->env = null; + $this->inParens = false; + $this->eatWhiteDefault = true; + $this->buffer = (string) $buffer; + + $this->saveEncoding(); + $this->extractLineNumbers($this->buffer); + + $isMediaQuery = $this->mediaQueryList($out); + + $this->restoreEncoding(); + + return $isMediaQuery; + } + + /** + * Parse a single chunk off the head of the buffer and append it to the + * current parse environment. + * + * Returns false when the buffer is empty, or when there is an error. + * + * This function is called repeatedly until the entire document is + * parsed. + * + * This parser is most similar to a recursive descent parser. Single + * functions represent discrete grammatical rules for the language, and + * they are able to capture the text that represents those rules. + * + * Consider the function Compiler::keyword(). (All parse functions are + * structured the same.) + * + * The function takes a single reference argument. When calling the + * function it will attempt to match a keyword on the head of the buffer. + * If it is successful, it will place the keyword in the referenced + * argument, advance the position in the buffer, and return true. If it + * fails then it won't advance the buffer and it will return false. + * + * All of these parse functions are powered by Compiler::match(), which behaves + * the same way, but takes a literal regular expression. Sometimes it is + * more convenient to use match instead of creating a new function. + * + * Because of the format of the functions, to parse an entire string of + * grammatical rules, you can chain them together using &&. + * + * But, if some of the rules in the chain succeed before one fails, then + * the buffer position will be left at an invalid state. In order to + * avoid this, Compiler::seek() is used to remember and set buffer positions. + * + * Before parsing a chain, use $s = $this->count to remember the current + * position into $s. Then if a chain fails, use $this->seek($s) to + * go back where we started. + * + * @return boolean + */ + protected function parseChunk() + { + $s = $this->count; + + // the directives + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') { + if ( + $this->literal('@at-root', 8) && + ($this->selectors($selector) || true) && + ($this->map($with) || true) && + (($this->matchChar('(') && + $this->interpolation($with) && + $this->matchChar(')')) || true) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s); + $atRoot->selector = $selector; + $atRoot->with = $with; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@media', 6) && + $this->mediaQueryList($mediaQueryList) && + $this->matchChar('{', false) + ) { + $media = $this->pushSpecialBlock(Type::T_MEDIA, $s); + $media->queryList = $mediaQueryList[2]; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@mixin', 6) && + $this->keyword($mixinName) && + ($this->argumentDef($args) || true) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s); + $mixin->name = $mixinName; + $mixin->args = $args; + + return true; + } + + $this->seek($s); + + if ( + ($this->literal('@include', 8) && + $this->keyword($mixinName) && + ($this->matchChar('(') && + ($this->argValues($argValues) || true) && + $this->matchChar(')') || true) && + ($this->end()) || + ($this->literal('using', 5) && + $this->argumentDef($argUsing) && + ($this->end() || $this->matchChar('{') && $hasBlock = true)) || + $this->matchChar('{') && $hasBlock = true) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $child = [ + Type::T_INCLUDE, + $mixinName, + isset($argValues) ? $argValues : null, + null, + isset($argUsing) ? $argUsing : null + ]; + + if (! empty($hasBlock)) { + $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s); + $include->child = $child; + } else { + $this->append($child, $s); + } + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@scssphp-import-once', 20) && + $this->valueList($importPath) && + $this->end() + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + list($line, $column) = $this->getSourcePosition($s); + $file = $this->sourceName; + $this->logger->warn("The \"@scssphp-import-once\" directive is deprecated and will be removed in ScssPhp 2.0, in \"$file\", line $line, column $column.", true); + + $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@import', 7) && + $this->valueList($importPath) && + $importPath[0] !== Type::T_FUNCTION_CALL && + $this->end() + ) { + if ($this->cssOnly) { + $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s); + $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]); + return true; + } + + $this->append([Type::T_IMPORT, $importPath], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@import', 7) && + $this->url($importPath) && + $this->end() + ) { + if ($this->cssOnly) { + $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s); + $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]); + return true; + } + + $this->append([Type::T_IMPORT, $importPath], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@extend', 7) && + $this->selectors($selectors) && + $this->end() + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + // check for '!flag' + $optional = $this->stripOptionalFlag($selectors); + $this->append([Type::T_EXTEND, $selectors, $optional], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@function', 9) && + $this->keyword($fnName) && + $this->argumentDef($args) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s); + $func->name = $fnName; + $func->args = $args; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@return', 7) && + ($this->valueList($retVal) || true) && + $this->end() + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@each', 5) && + $this->genericList($varNames, 'variable', ',', false) && + $this->literal('in', 2) && + $this->valueList($list) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $each = $this->pushSpecialBlock(Type::T_EACH, $s); + + foreach ($varNames[2] as $varName) { + $each->vars[] = $varName[1]; + } + + $each->list = $list; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@while', 6) && + $this->expression($cond) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + while ( + $cond[0] === Type::T_LIST && + ! empty($cond['enclosing']) && + $cond['enclosing'] === 'parent' && + \count($cond[2]) == 1 + ) { + $cond = reset($cond[2]); + } + + $while = $this->pushSpecialBlock(Type::T_WHILE, $s); + $while->cond = $cond; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@for', 4) && + $this->variable($varName) && + $this->literal('from', 4) && + $this->expression($start) && + ($this->literal('through', 7) || + ($forUntil = true && $this->literal('to', 2))) && + $this->expression($end) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $for = $this->pushSpecialBlock(Type::T_FOR, $s); + $for->var = $varName[1]; + $for->start = $start; + $for->end = $end; + $for->until = isset($forUntil); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@if', 3) && + $this->functionCallArgumentsList($cond, false, '{', false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $if = $this->pushSpecialBlock(Type::T_IF, $s); + + while ( + $cond[0] === Type::T_LIST && + ! empty($cond['enclosing']) && + $cond['enclosing'] === 'parent' && + \count($cond[2]) == 1 + ) { + $cond = reset($cond[2]); + } + + $if->cond = $cond; + $if->cases = []; + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@debug', 6) && + $this->functionCallArgumentsList($value, false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $this->append([Type::T_DEBUG, $value], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@warn', 5) && + $this->functionCallArgumentsList($value, false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $this->append([Type::T_WARN, $value], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@error', 6) && + $this->functionCallArgumentsList($value, false) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $this->append([Type::T_ERROR, $value], $s); + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@content', 8) && + ($this->end() || + $this->matchChar('(') && + $this->argValues($argContent) && + $this->matchChar(')') && + $this->end()) + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + $this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s); + + return true; + } + + $this->seek($s); + + $last = $this->last(); + + if (isset($last) && $last[0] === Type::T_IF) { + list(, $if) = $last; + + if ($this->literal('@else', 5)) { + if ($this->matchChar('{', false)) { + $else = $this->pushSpecialBlock(Type::T_ELSE, $s); + } elseif ( + $this->literal('if', 2) && + $this->functionCallArgumentsList($cond, false, '{', false) + ) { + $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s); + $else->cond = $cond; + } + + if (isset($else)) { + $else->dontAppend = true; + $if->cases[] = $else; + + return true; + } + } + + $this->seek($s); + } + + // only retain the first @charset directive encountered + if ( + $this->literal('@charset', 8) && + $this->valueList($charset) && + $this->end() + ) { + if (! isset($this->charset)) { + $statement = [Type::T_CHARSET, $charset]; + + list($line, $column) = $this->getSourcePosition($s); + + $statement[static::SOURCE_LINE] = $line; + $statement[static::SOURCE_COLUMN] = $column; + $statement[static::SOURCE_INDEX] = $this->sourceIndex; + + $this->charset = $statement; + } + + return true; + } + + $this->seek($s); + + if ( + $this->literal('@supports', 9) && + ($t1 = $this->supportsQuery($supportQuery)) && + ($t2 = $this->matchChar('{', false)) + ) { + $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s); + $directive->name = 'supports'; + $directive->value = $supportQuery; + + return true; + } + + $this->seek($s); + + // doesn't match built in directive, do generic one + if ( + $this->matchChar('@', false) && + $this->mixedKeyword($dirName) && + $this->directiveValue($dirValue, '{') + ) { + if (count($dirName) === 1 && is_string(reset($dirName))) { + $dirName = reset($dirName); + } else { + $dirName = [Type::T_STRING, '', $dirName]; + } + if ($dirName === 'media') { + $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s); + } else { + $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s); + $directive->name = $dirName; + } + + if (isset($dirValue)) { + ! $this->cssOnly || ($dirValue = $this->assertPlainCssValid($dirValue)); + $directive->value = $dirValue; + } + + return true; + } + + $this->seek($s); + + // maybe it's a generic blockless directive + if ( + $this->matchChar('@', false) && + $this->mixedKeyword($dirName) && + ! $this->isKnownGenericDirective($dirName) && + ($this->end(false) || ($this->directiveValue($dirValue, '') && $this->end(false))) + ) { + if (\count($dirName) === 1 && \is_string(\reset($dirName))) { + $dirName = \reset($dirName); + } else { + $dirName = [Type::T_STRING, '', $dirName]; + } + if ( + ! empty($this->env->parent) && + $this->env->type && + ! \in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]) + ) { + $plain = \trim(\substr($this->buffer, $s, $this->count - $s)); + throw $this->parseError( + "Unknown directive `{$plain}` not allowed in `" . $this->env->type . "` block" + ); + } + // blockless directives with a blank line after keeps their blank lines after + // sass-spec compliance purpose + $s = $this->count; + $hasBlankLine = false; + if ($this->match('\s*?\n\s*\n', $out, false)) { + $hasBlankLine = true; + $this->seek($s); + } + $isNotRoot = ! empty($this->env->parent); + $this->append([Type::T_DIRECTIVE, [$dirName, $dirValue, $hasBlankLine, $isNotRoot]], $s); + $this->whitespace(); + + return true; + } + + $this->seek($s); + + return false; + } + + $inCssSelector = null; + if ($this->cssOnly) { + $inCssSelector = (! empty($this->env->parent) && + ! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA])); + } + // custom properties : right part is static + if (($this->customProperty($name) ) && $this->matchChar(':', false)) { + $start = $this->count; + + // but can be complex and finish with ; or } + foreach ([';','}'] as $ending) { + if ( + $this->openString($ending, $stringValue, '(', ')', false) && + $this->end() + ) { + $end = $this->count; + $value = $stringValue; + + // check if we have only a partial value due to nested [] or { } to take in account + $nestingPairs = [['[', ']'], ['{', '}']]; + + foreach ($nestingPairs as $nestingPair) { + $p = strpos($this->buffer, $nestingPair[0], $start); + + if ($p && $p < $end) { + $this->seek($start); + + if ( + $this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) && + $this->end() && + $this->count > $end + ) { + $end = $this->count; + $value = $stringValue; + } + } + } + + $this->seek($end); + $this->append([Type::T_CUSTOM_PROPERTY, $name, $value], $s); + + return true; + } + } + + // TODO: output an error here if nothing found according to sass spec + } + + $this->seek($s); + + // property shortcut + // captures most properties before having to parse a selector + if ( + $this->keyword($name, false) && + $this->literal(': ', 2) && + $this->valueList($value) && + $this->end() + ) { + $name = [Type::T_STRING, '', [$name]]; + $this->append([Type::T_ASSIGN, $name, $value], $s); + + return true; + } + + $this->seek($s); + + // variable assigns + if ( + $this->variable($name) && + $this->matchChar(':') && + $this->valueList($value) && + $this->end() + ) { + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + + // check for '!flag' + $assignmentFlags = $this->stripAssignmentFlags($value); + $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s); + + return true; + } + + $this->seek($s); + + // opening css block + if ( + $this->selectors($selectors) && + $this->matchChar('{', false) + ) { + ! $this->cssOnly || ! $inCssSelector || $this->assertPlainCssValid(false); + + $this->pushBlock($selectors, $s); + + if ($this->eatWhiteDefault) { + $this->whitespace(); + $this->append(null); // collect comments at the beginning if needed + } + + return true; + } + + $this->seek($s); + + // property assign, or nested assign + if ( + $this->propertyName($name) && + $this->matchChar(':') + ) { + $foundSomething = false; + + if ($this->valueList($value)) { + if (empty($this->env->parent)) { + throw $this->parseError('expected "{"'); + } + + $this->append([Type::T_ASSIGN, $name, $value], $s); + $foundSomething = true; + } + + if ($this->matchChar('{', false)) { + ! $this->cssOnly || $this->assertPlainCssValid(false); + + $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s); + $propBlock->prefix = $name; + $propBlock->hasValue = $foundSomething; + + $foundSomething = true; + } elseif ($foundSomething) { + $foundSomething = $this->end(); + } + + if ($foundSomething) { + return true; + } + } + + $this->seek($s); + + // closing a block + if ($this->matchChar('}', false)) { + $block = $this->popBlock(); + + if (! isset($block->type) || $block->type !== Type::T_IF) { + if ($this->env->parent) { + $this->append(null); // collect comments before next statement if needed + } + } + + if (isset($block->type) && $block->type === Type::T_INCLUDE) { + $include = $block->child; + unset($block->child); + $include[3] = $block; + $this->append($include, $s); + } elseif (empty($block->dontAppend)) { + $type = isset($block->type) ? $block->type : Type::T_BLOCK; + $this->append([$type, $block], $s); + } + + // collect comments just after the block closing if needed + if ($this->eatWhiteDefault) { + $this->whitespace(); + + if ($this->env->comments) { + $this->append(null); + } + } + + return true; + } + + // extra stuff + if ($this->matchChar(';')) { + return true; + } + + return false; + } + + /** + * Push block onto parse tree + * + * @param array|null $selectors + * @param integer $pos + * + * @return Block + */ + protected function pushBlock($selectors, $pos = 0) + { + list($line, $column) = $this->getSourcePosition($pos); + + $b = new Block(); + $b->sourceName = $this->sourceName; + $b->sourceLine = $line; + $b->sourceColumn = $column; + $b->sourceIndex = $this->sourceIndex; + $b->selectors = $selectors; + $b->comments = []; + $b->parent = $this->env; + + if (! $this->env) { + $b->children = []; + } elseif (empty($this->env->children)) { + $this->env->children = $this->env->comments; + $b->children = []; + $this->env->comments = []; + } else { + $b->children = $this->env->comments; + $this->env->comments = []; + } + + $this->env = $b; + + // collect comments at the beginning of a block if needed + if ($this->eatWhiteDefault) { + $this->whitespace(); + + if ($this->env->comments) { + $this->append(null); + } + } + + return $b; + } + + /** + * Push special (named) block onto parse tree + * + * @param string $type + * @param integer $pos + * + * @return Block + */ + protected function pushSpecialBlock($type, $pos) + { + $block = $this->pushBlock(null, $pos); + $block->type = $type; + + return $block; + } + + /** + * Pop scope and return last block + * + * @return Block + * + * @throws \Exception + */ + protected function popBlock() + { + + // collect comments ending just before of a block closing + if ($this->env->comments) { + $this->append(null); + } + + // pop the block + $block = $this->env; + + if (empty($block->parent)) { + throw $this->parseError('unexpected }'); + } + + if ($block->type == Type::T_AT_ROOT) { + // keeps the parent in case of self selector & + $block->selfParent = $block->parent; + } + + $this->env = $block->parent; + + unset($block->parent); + + return $block; + } + + /** + * Peek input stream + * + * @param string $regex + * @param array $out + * @param integer $from + * + * @return integer + */ + protected function peek($regex, &$out, $from = null) + { + if (! isset($from)) { + $from = $this->count; + } + + $r = '/' . $regex . '/' . $this->patternModifiers; + $result = preg_match($r, $this->buffer, $out, 0, $from); + + return $result; + } + + /** + * Seek to position in input stream (or return current position in input stream) + * + * @param integer $where + */ + protected function seek($where) + { + $this->count = $where; + } + + /** + * Assert a parsed part is plain CSS Valid + * + * @param array|false $parsed + * @param int $startPos + * @throws ParserException + */ + protected function assertPlainCssValid($parsed, $startPos = null) + { + $type = ''; + if ($parsed) { + $type = $parsed[0]; + $parsed = $this->isPlainCssValidElement($parsed); + } + if (! $parsed) { + if (! \is_null($startPos)) { + $plain = rtrim(substr($this->buffer, $startPos, $this->count - $startPos)); + $message = "Error : `{$plain}` isn't allowed in plain CSS"; + } else { + $message = 'Error: SCSS syntax not allowed in CSS file'; + } + if ($type) { + $message .= " ($type)"; + } + throw $this->parseError($message); + } + + return $parsed; + } + + /** + * Check a parsed element is plain CSS Valid + * @param array $parsed + * @return bool|array + */ + protected function isPlainCssValidElement($parsed, $allowExpression = false) + { + // keep string as is + if (is_string($parsed)) { + return $parsed; + } + + if ( + \in_array($parsed[0], [Type::T_FUNCTION, Type::T_FUNCTION_CALL]) && + !\in_array($parsed[1], [ + 'alpha', + 'attr', + 'calc', + 'cubic-bezier', + 'env', + 'grayscale', + 'hsl', + 'hsla', + 'hwb', + 'invert', + 'linear-gradient', + 'min', + 'max', + 'radial-gradient', + 'repeating-linear-gradient', + 'repeating-radial-gradient', + 'rgb', + 'rgba', + 'rotate', + 'saturate', + 'var', + ]) && + Compiler::isNativeFunction($parsed[1]) + ) { + return false; + } + + switch ($parsed[0]) { + case Type::T_BLOCK: + case Type::T_KEYWORD: + case Type::T_NULL: + case Type::T_NUMBER: + case Type::T_MEDIA: + return $parsed; + + case Type::T_COMMENT: + if (isset($parsed[2])) { + return false; + } + return $parsed; + + case Type::T_DIRECTIVE: + if (\is_array($parsed[1])) { + $parsed[1][1] = $this->isPlainCssValidElement($parsed[1][1]); + if (! $parsed[1][1]) { + return false; + } + } + + return $parsed; + + case Type::T_IMPORT: + if ($parsed[1][0] === Type::T_LIST) { + return false; + } + $parsed[1] = $this->isPlainCssValidElement($parsed[1]); + if ($parsed[1] === false) { + return false; + } + return $parsed; + + case Type::T_STRING: + foreach ($parsed[2] as $k => $substr) { + if (\is_array($substr)) { + $parsed[2][$k] = $this->isPlainCssValidElement($substr); + if (! $parsed[2][$k]) { + return false; + } + } + } + return $parsed; + + case Type::T_LIST: + if (!empty($parsed['enclosing'])) { + return false; + } + foreach ($parsed[2] as $k => $listElement) { + $parsed[2][$k] = $this->isPlainCssValidElement($listElement); + if (! $parsed[2][$k]) { + return false; + } + } + return $parsed; + + case Type::T_ASSIGN: + foreach ([1, 2, 3] as $k) { + if (! empty($parsed[$k])) { + $parsed[$k] = $this->isPlainCssValidElement($parsed[$k]); + if (! $parsed[$k]) { + return false; + } + } + } + return $parsed; + + case Type::T_EXPRESSION: + list( ,$op, $lhs, $rhs, $inParens, $whiteBefore, $whiteAfter) = $parsed; + if (! $allowExpression && ! \in_array($op, ['and', 'or', '/'])) { + return false; + } + $lhs = $this->isPlainCssValidElement($lhs, true); + if (! $lhs) { + return false; + } + $rhs = $this->isPlainCssValidElement($rhs, true); + if (! $rhs) { + return false; + } + + return [ + Type::T_STRING, + '', [ + $this->inParens ? '(' : '', + $lhs, + ($whiteBefore ? ' ' : '') . $op . ($whiteAfter ? ' ' : ''), + $rhs, + $this->inParens ? ')' : '' + ] + ]; + + case Type::T_CUSTOM_PROPERTY: + case Type::T_UNARY: + $parsed[2] = $this->isPlainCssValidElement($parsed[2]); + if (! $parsed[2]) { + return false; + } + return $parsed; + + case Type::T_FUNCTION: + $argsList = $parsed[2]; + foreach ($argsList[2] as $argElement) { + if (! $this->isPlainCssValidElement($argElement)) { + return false; + } + } + return $parsed; + + case Type::T_FUNCTION_CALL: + $parsed[0] = Type::T_FUNCTION; + $argsList = [Type::T_LIST, ',', []]; + foreach ($parsed[2] as $arg) { + if ($arg[0] || ! empty($arg[2])) { + // no named arguments possible in a css function call + // nor ... argument + return false; + } + $arg = $this->isPlainCssValidElement($arg[1], $parsed[1] === 'calc'); + if (! $arg) { + return false; + } + $argsList[2][] = $arg; + } + $parsed[2] = $argsList; + return $parsed; + } + + return false; + } + + /** + * Match string looking for either ending delim, escape, or string interpolation + * + * {@internal This is a workaround for preg_match's 250K string match limit. }} + * + * @param array $m Matches (passed by reference) + * @param string $delim Delimiter + * + * @return boolean True if match; false otherwise + */ + protected function matchString(&$m, $delim) + { + $token = null; + + $end = \strlen($this->buffer); + + // look for either ending delim, escape, or string interpolation + foreach (['#{', '\\', "\r", $delim] as $lookahead) { + $pos = strpos($this->buffer, $lookahead, $this->count); + + if ($pos !== false && $pos < $end) { + $end = $pos; + $token = $lookahead; + } + } + + if (! isset($token)) { + return false; + } + + $match = substr($this->buffer, $this->count, $end - $this->count); + $m = [ + $match . $token, + $match, + $token + ]; + $this->count = $end + \strlen($token); + + return true; + } + + /** + * Try to match something on head of buffer + * + * @param string $regex + * @param array $out + * @param boolean $eatWhitespace + * + * @return boolean + */ + protected function match($regex, &$out, $eatWhitespace = null) + { + $r = '/' . $regex . '/' . $this->patternModifiers; + + if (! preg_match($r, $this->buffer, $out, 0, $this->count)) { + return false; + } + + $this->count += \strlen($out[0]); + + if (! isset($eatWhitespace)) { + $eatWhitespace = $this->eatWhiteDefault; + } + + if ($eatWhitespace) { + $this->whitespace(); + } + + return true; + } + + /** + * Match a single string + * + * @param string $char + * @param boolean $eatWhitespace + * + * @return boolean + */ + protected function matchChar($char, $eatWhitespace = null) + { + if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) { + return false; + } + + $this->count++; + + if (! isset($eatWhitespace)) { + $eatWhitespace = $this->eatWhiteDefault; + } + + if ($eatWhitespace) { + $this->whitespace(); + } + + return true; + } + + /** + * Match literal string + * + * @param string $what + * @param integer $len + * @param boolean $eatWhitespace + * + * @return boolean + */ + protected function literal($what, $len, $eatWhitespace = null) + { + if (strcasecmp(substr($this->buffer, $this->count, $len), $what) !== 0) { + return false; + } + + $this->count += $len; + + if (! isset($eatWhitespace)) { + $eatWhitespace = $this->eatWhiteDefault; + } + + if ($eatWhitespace) { + $this->whitespace(); + } + + return true; + } + + /** + * Match some whitespace + * + * @return boolean + */ + protected function whitespace() + { + $gotWhite = false; + + while (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) { + if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { + // comment that are kept in the output CSS + $comment = []; + $startCommentCount = $this->count; + $endCommentCount = $this->count + \strlen($m[1]); + + // find interpolations in comment + $p = strpos($this->buffer, '#{', $this->count); + + while ($p !== false && $p < $endCommentCount) { + $c = substr($this->buffer, $this->count, $p - $this->count); + $comment[] = $c; + $this->count = $p; + $out = null; + + if ($this->interpolation($out)) { + // keep right spaces in the following string part + if ($out[3]) { + while ($this->buffer[$this->count - 1] !== '}') { + $this->count--; + } + + $out[3] = ''; + } + + $comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out]; + } else { + list($line, $column) = $this->getSourcePosition($this->count); + $file = $this->sourceName; + if (!$this->discardComments) { + $this->logger->warn("Unterminated interpolations in multiline comments are deprecated and will be removed in ScssPhp 2.0, in \"$file\", line $line, column $column.", true); + } + $comment[] = substr($this->buffer, $this->count, 2); + + $this->count += 2; + } + + $p = strpos($this->buffer, '#{', $this->count); + } + + // remaining part + $c = substr($this->buffer, $this->count, $endCommentCount - $this->count); + + if (! $comment) { + // single part static comment + $this->appendComment([Type::T_COMMENT, $c]); + } else { + $comment[] = $c; + $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount); + $commentStatement = [Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]]; + + list($line, $column) = $this->getSourcePosition($startCommentCount); + $commentStatement[self::SOURCE_LINE] = $line; + $commentStatement[self::SOURCE_COLUMN] = $column; + $commentStatement[self::SOURCE_INDEX] = $this->sourceIndex; + + $this->appendComment($commentStatement); + } + + $this->commentsSeen[$startCommentCount] = true; + $this->count = $endCommentCount; + } else { + // comment that are ignored and not kept in the output css + $this->count += \strlen($m[0]); + // silent comments are not allowed in plain CSS files + ! $this->cssOnly + || ! \strlen(trim($m[0])) + || $this->assertPlainCssValid(false, $this->count - \strlen($m[0])); + } + + $gotWhite = true; + } + + return $gotWhite; + } + + /** + * Append comment to current block + * + * @param array $comment + */ + protected function appendComment($comment) + { + if (! $this->discardComments) { + $this->env->comments[] = $comment; + } + } + + /** + * Append statement to current block + * + * @param array|null $statement + * @param integer $pos + */ + protected function append($statement, $pos = null) + { + if (! \is_null($statement)) { + ! $this->cssOnly || ($statement = $this->assertPlainCssValid($statement, $pos)); + + if (! \is_null($pos)) { + list($line, $column) = $this->getSourcePosition($pos); + + $statement[static::SOURCE_LINE] = $line; + $statement[static::SOURCE_COLUMN] = $column; + $statement[static::SOURCE_INDEX] = $this->sourceIndex; + } + + $this->env->children[] = $statement; + } + + $comments = $this->env->comments; + + if ($comments) { + $this->env->children = array_merge($this->env->children, $comments); + $this->env->comments = []; + } + } + + /** + * Returns last child was appended + * + * @return array|null + */ + protected function last() + { + $i = \count($this->env->children) - 1; + + if (isset($this->env->children[$i])) { + return $this->env->children[$i]; + } + } + + /** + * Parse media query list + * + * @param array $out + * + * @return boolean + */ + protected function mediaQueryList(&$out) + { + return $this->genericList($out, 'mediaQuery', ',', false); + } + + /** + * Parse media query + * + * @param array $out + * + * @return boolean + */ + protected function mediaQuery(&$out) + { + $expressions = null; + $parts = []; + + if ( + ($this->literal('only', 4) && ($only = true) || + $this->literal('not', 3) && ($not = true) || true) && + $this->mixedKeyword($mediaType) + ) { + $prop = [Type::T_MEDIA_TYPE]; + + if (isset($only)) { + $prop[] = [Type::T_KEYWORD, 'only']; + } + + if (isset($not)) { + $prop[] = [Type::T_KEYWORD, 'not']; + } + + $media = [Type::T_LIST, '', []]; + + foreach ((array) $mediaType as $type) { + if (\is_array($type)) { + $media[2][] = $type; + } else { + $media[2][] = [Type::T_KEYWORD, $type]; + } + } + + $prop[] = $media; + $parts[] = $prop; + } + + if (empty($parts) || $this->literal('and', 3)) { + $this->genericList($expressions, 'mediaExpression', 'and', false); + + if (\is_array($expressions)) { + $parts = array_merge($parts, $expressions[2]); + } + } + + $out = $parts; + + return true; + } + + /** + * Parse supports query + * + * @param array $out + * + * @return boolean + */ + protected function supportsQuery(&$out) + { + $expressions = null; + $parts = []; + + $s = $this->count; + + $not = false; + + if ( + ($this->literal('not', 3) && ($not = true) || true) && + $this->matchChar('(') && + ($this->expression($property)) && + $this->literal(': ', 2) && + $this->valueList($value) && + $this->matchChar(')') + ) { + $support = [Type::T_STRING, '', [[Type::T_KEYWORD, ($not ? 'not ' : '') . '(']]]; + $support[2][] = $property; + $support[2][] = [Type::T_KEYWORD, ': ']; + $support[2][] = $value; + $support[2][] = [Type::T_KEYWORD, ')']; + + $parts[] = $support; + $s = $this->count; + } else { + $this->seek($s); + } + + if ( + $this->matchChar('(') && + $this->supportsQuery($subQuery) && + $this->matchChar(')') + ) { + $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, '('], $subQuery, [Type::T_KEYWORD, ')']]]; + $s = $this->count; + } else { + $this->seek($s); + } + + if ( + $this->literal('not', 3) && + $this->supportsQuery($subQuery) + ) { + $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]]; + $s = $this->count; + } else { + $this->seek($s); + } + + if ( + $this->literal('selector(', 9) && + $this->selector($selector) && + $this->matchChar(')') + ) { + $support = [Type::T_STRING, '', [[Type::T_KEYWORD, 'selector(']]]; + + $selectorList = [Type::T_LIST, '', []]; + + foreach ($selector as $sc) { + $compound = [Type::T_STRING, '', []]; + + foreach ($sc as $scp) { + if (\is_array($scp)) { + $compound[2][] = $scp; + } else { + $compound[2][] = [Type::T_KEYWORD, $scp]; + } + } + + $selectorList[2][] = $compound; + } + + $support[2][] = $selectorList; + $support[2][] = [Type::T_KEYWORD, ')']; + $parts[] = $support; + $s = $this->count; + } else { + $this->seek($s); + } + + if ($this->variable($var) or $this->interpolation($var)) { + $parts[] = $var; + $s = $this->count; + } else { + $this->seek($s); + } + + if ( + $this->literal('and', 3) && + $this->genericList($expressions, 'supportsQuery', ' and', false) + ) { + array_unshift($expressions[2], [Type::T_STRING, '', $parts]); + + $parts = [$expressions]; + $s = $this->count; + } else { + $this->seek($s); + } + + if ( + $this->literal('or', 2) && + $this->genericList($expressions, 'supportsQuery', ' or', false) + ) { + array_unshift($expressions[2], [Type::T_STRING, '', $parts]); + + $parts = [$expressions]; + $s = $this->count; + } else { + $this->seek($s); + } + + if (\count($parts)) { + if ($this->eatWhiteDefault) { + $this->whitespace(); + } + + $out = [Type::T_STRING, '', $parts]; + + return true; + } + + return false; + } + + + /** + * Parse media expression + * + * @param array $out + * + * @return boolean + */ + protected function mediaExpression(&$out) + { + $s = $this->count; + $value = null; + + if ( + $this->matchChar('(') && + $this->expression($feature) && + ($this->matchChar(':') && + $this->expression($value) || true) && + $this->matchChar(')') + ) { + $out = [Type::T_MEDIA_EXPRESSION, $feature]; + + if ($value) { + $out[] = $value; + } + + return true; + } + + $this->seek($s); + + return false; + } + + /** + * Parse argument values + * + * @param array $out + * + * @return boolean + */ + protected function argValues(&$out) + { + $discardComments = $this->discardComments; + $this->discardComments = true; + + if ($this->genericList($list, 'argValue', ',', false)) { + $out = $list[2]; + + $this->discardComments = $discardComments; + + return true; + } + + $this->discardComments = $discardComments; + + return false; + } + + /** + * Parse argument value + * + * @param array $out + * + * @return boolean + */ + protected function argValue(&$out) + { + $s = $this->count; + + $keyword = null; + + if (! $this->variable($keyword) || ! $this->matchChar(':')) { + $this->seek($s); + + $keyword = null; + } + + if ($this->genericList($value, 'expression', '', true)) { + $out = [$keyword, $value, false]; + $s = $this->count; + + if ($this->literal('...', 3)) { + $out[2] = true; + } else { + $this->seek($s); + } + + return true; + } + + return false; + } + + /** + * Check if a generic directive is known to be able to allow almost any syntax or not + * @param mixed $directiveName + * @return bool + */ + protected function isKnownGenericDirective($directiveName) + { + if (\is_array($directiveName) && \is_string(reset($directiveName))) { + $directiveName = reset($directiveName); + } + if (! \is_string($directiveName)) { + return false; + } + if ( + \in_array($directiveName, [ + 'at-root', + 'media', + 'mixin', + 'include', + 'scssphp-import-once', + 'import', + 'extend', + 'function', + 'break', + 'continue', + 'return', + 'each', + 'while', + 'for', + 'if', + 'debug', + 'warn', + 'error', + 'content', + 'else', + 'charset', + 'supports', + // Todo + 'use', + 'forward', + ]) + ) { + return true; + } + return false; + } + + /** + * Parse directive value list that considers $vars as keyword + * + * @param array $out + * @param boolean|string $endChar + * + * @return boolean + */ + protected function directiveValue(&$out, $endChar = false) + { + $s = $this->count; + + if ($this->variable($out)) { + if ($endChar && $this->matchChar($endChar, false)) { + return true; + } + + if (! $endChar && $this->end()) { + return true; + } + } + + $this->seek($s); + + if (\is_string($endChar) && $this->openString($endChar ? $endChar : ';', $out, null, null, true, ";}{")) { + if ($endChar && $this->matchChar($endChar, false)) { + return true; + } + $ss = $this->count; + if (!$endChar && $this->end()) { + $this->seek($ss); + return true; + } + } + + $this->seek($s); + + $allowVars = $this->allowVars; + $this->allowVars = false; + + $res = $this->genericList($out, 'spaceList', ','); + $this->allowVars = $allowVars; + + if ($res) { + if ($endChar && $this->matchChar($endChar, false)) { + return true; + } + + if (! $endChar && $this->end()) { + return true; + } + } + + $this->seek($s); + + if ($endChar && $this->matchChar($endChar, false)) { + return true; + } + + return false; + } + + /** + * Parse comma separated value list + * + * @param array $out + * + * @return boolean + */ + protected function valueList(&$out) + { + $discardComments = $this->discardComments; + $this->discardComments = true; + $res = $this->genericList($out, 'spaceList', ','); + $this->discardComments = $discardComments; + + return $res; + } + + /** + * Parse a function call, where externals () are part of the call + * and not of the value list + * + * @param $out + * @param bool $mandatoryEnclos + * @param null|string $charAfter + * @param null|bool $eatWhiteSp + * @return bool + */ + protected function functionCallArgumentsList(&$out, $mandatoryEnclos = true, $charAfter = null, $eatWhiteSp = null) + { + $s = $this->count; + + if ( + $this->matchChar('(') && + $this->valueList($out) && + $this->matchChar(')') && + ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end()) + ) { + return true; + } + + if (! $mandatoryEnclos) { + $this->seek($s); + + if ( + $this->valueList($out) && + ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end()) + ) { + return true; + } + } + + $this->seek($s); + + return false; + } + + /** + * Parse space separated value list + * + * @param array $out + * + * @return boolean + */ + protected function spaceList(&$out) + { + return $this->genericList($out, 'expression'); + } + + /** + * Parse generic list + * + * @param array $out + * @param string $parseItem The name of the method used to parse items + * @param string $delim + * @param boolean $flatten + * + * @return boolean + */ + protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) + { + $s = $this->count; + $items = []; + $value = null; + + while ($this->$parseItem($value)) { + $trailing_delim = false; + $items[] = $value; + + if ($delim) { + if (! $this->literal($delim, \strlen($delim))) { + break; + } + + $trailing_delim = true; + } else { + // if no delim watch that a keyword didn't eat the single/double quote + // from the following starting string + if ($value[0] === Type::T_KEYWORD) { + $word = $value[1]; + + $last_char = substr($word, -1); + + if ( + strlen($word) > 1 && + in_array($last_char, [ "'", '"']) && + substr($word, -2, 1) !== '\\' + ) { + // if there is a non escaped opening quote in the keyword, this seems unlikely a mistake + $word = str_replace('\\' . $last_char, '\\\\', $word); + if (strpos($word, $last_char) < strlen($word) - 1) { + continue; + } + + $currentCount = $this->count; + + // let's try to rewind to previous char and try a parse + $this->count--; + // in case the keyword also eat spaces + while (substr($this->buffer, $this->count, 1) !== $last_char) { + $this->count--; + } + + $nextValue = null; + if ($this->$parseItem($nextValue)) { + if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) { + // bad try, forget it + $this->seek($currentCount); + continue; + } + if ($nextValue[0] !== Type::T_STRING) { + // bad try, forget it + $this->seek($currentCount); + continue; + } + + // OK it was a good idea + $value[1] = substr($value[1], 0, -1); + array_pop($items); + $items[] = $value; + $items[] = $nextValue; + } else { + // bad try, forget it + $this->seek($currentCount); + continue; + } + } + } + } + } + + if (! $items) { + $this->seek($s); + + return false; + } + + if ($trailing_delim) { + $items[] = [Type::T_NULL]; + } + + if ($flatten && \count($items) === 1) { + $out = $items[0]; + } else { + $out = [Type::T_LIST, $delim, $items]; + } + + return true; + } + + /** + * Parse expression + * + * @param array $out + * @param boolean $listOnly + * @param boolean $lookForExp + * + * @return boolean + */ + protected function expression(&$out, $listOnly = false, $lookForExp = true) + { + $s = $this->count; + $discard = $this->discardComments; + $this->discardComments = true; + $allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]); + + if ($this->matchChar('(')) { + if ($this->enclosedExpression($lhs, $s, ')', $allowedTypes)) { + if ($lookForExp) { + $out = $this->expHelper($lhs, 0); + } else { + $out = $lhs; + } + + $this->discardComments = $discard; + + return true; + } + + $this->seek($s); + } + + if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) { + if ($this->enclosedExpression($lhs, $s, ']', [Type::T_LIST])) { + if ($lookForExp) { + $out = $this->expHelper($lhs, 0); + } else { + $out = $lhs; + } + + $this->discardComments = $discard; + + return true; + } + + $this->seek($s); + } + + if (! $listOnly && $this->value($lhs)) { + if ($lookForExp) { + $out = $this->expHelper($lhs, 0); + } else { + $out = $lhs; + } + + $this->discardComments = $discard; + + return true; + } + + $this->discardComments = $discard; + + return false; + } + + /** + * Parse expression specifically checking for lists in parenthesis or brackets + * + * @param array $out + * @param integer $s + * @param string $closingParen + * @param array $allowedTypes + * + * @return boolean + */ + protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP]) + { + if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) { + $out = [Type::T_LIST, '', []]; + + switch ($closingParen) { + case ')': + $out['enclosing'] = 'parent'; // parenthesis list + break; + + case ']': + $out['enclosing'] = 'bracket'; // bracketed list + break; + } + + return true; + } + + if ( + $this->valueList($out) && + $this->matchChar($closingParen) && ! ($closingParen === ')' && + \in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) && + \in_array(Type::T_LIST, $allowedTypes) + ) { + if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) { + $out = [Type::T_LIST, '', [$out]]; + } + + switch ($closingParen) { + case ')': + $out['enclosing'] = 'parent'; // parenthesis list + break; + + case ']': + $out['enclosing'] = 'bracket'; // bracketed list + break; + } + + return true; + } + + $this->seek($s); + + if (\in_array(Type::T_MAP, $allowedTypes) && $this->map($out)) { + return true; + } + + return false; + } + + /** + * Parse left-hand side of subexpression + * + * @param array $lhs + * @param integer $minP + * + * @return array + */ + protected function expHelper($lhs, $minP) + { + $operators = static::$operatorPattern; + + $ss = $this->count; + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) { + $whiteAfter = isset($this->buffer[$this->count]) && + ctype_space($this->buffer[$this->count]); + $varAfter = isset($this->buffer[$this->count]) && + $this->buffer[$this->count] === '$'; + + $this->whitespace(); + + $op = $m[1]; + + // don't turn negative numbers into expressions + if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) { + break; + } + + if (! $this->value($rhs) && ! $this->expression($rhs, true, false)) { + break; + } + + if ($op === '-' && ! $whiteAfter && $rhs[0] === Type::T_KEYWORD) { + break; + } + + // consume higher-precedence operators on the right-hand side + $rhs = $this->expHelper($rhs, static::$precedence[$op] + 1); + + $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter]; + + $ss = $this->count; + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + } + + $this->seek($ss); + + return $lhs; + } + + /** + * Parse value + * + * @param array $out + * + * @return boolean + */ + protected function value(&$out) + { + if (! isset($this->buffer[$this->count])) { + return false; + } + + $s = $this->count; + $char = $this->buffer[$this->count]; + + if ( + $this->literal('url(', 4) && + $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false) + ) { + $len = strspn( + $this->buffer, + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=', + $this->count + ); + + $this->count += $len; + + if ($this->matchChar(')')) { + $content = substr($this->buffer, $s, $this->count - $s); + $out = [Type::T_KEYWORD, $content]; + + return true; + } + } + + $this->seek($s); + + if ( + $this->literal('url(', 4, false) && + $this->match('\s*(\/\/[^\s\)]+)\s*', $m) + ) { + $content = 'url(' . $m[1]; + + if ($this->matchChar(')')) { + $content .= ')'; + $out = [Type::T_KEYWORD, $content]; + + return true; + } + } + + $this->seek($s); + + // not + if ($char === 'n' && $this->literal('not', 3, false)) { + if ( + $this->whitespace() && + $this->value($inner) + ) { + $out = [Type::T_UNARY, 'not', $inner, $this->inParens]; + + return true; + } + + $this->seek($s); + + if ($this->parenValue($inner)) { + $out = [Type::T_UNARY, 'not', $inner, $this->inParens]; + + return true; + } + + $this->seek($s); + } + + // addition + if ($char === '+') { + $this->count++; + + $follow_white = $this->whitespace(); + + if ($this->value($inner)) { + $out = [Type::T_UNARY, '+', $inner, $this->inParens]; + + return true; + } + + if ($follow_white) { + $out = [Type::T_KEYWORD, $char]; + return true; + } + + $this->seek($s); + + return false; + } + + // negation + if ($char === '-') { + if ($this->customProperty($out)) { + return true; + } + + $this->count++; + + $follow_white = $this->whitespace(); + + if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) { + $out = [Type::T_UNARY, '-', $inner, $this->inParens]; + + return true; + } + + if ( + $this->keyword($inner) && + ! $this->func($inner, $out) + ) { + $out = [Type::T_UNARY, '-', $inner, $this->inParens]; + + return true; + } + + if ($follow_white) { + $out = [Type::T_KEYWORD, $char]; + + return true; + } + + $this->seek($s); + } + + // paren + if ($char === '(' && $this->parenValue($out)) { + return true; + } + + if ($char === '#') { + if ($this->interpolation($out) || $this->color($out)) { + return true; + } + + $this->count++; + + if ($this->keyword($keyword)) { + $out = [Type::T_KEYWORD, '#' . $keyword]; + + return true; + } + + $this->count--; + } + + if ($this->matchChar('&', true)) { + $out = [Type::T_SELF]; + + return true; + } + + if ($char === '$' && $this->variable($out)) { + return true; + } + + if ($char === 'p' && $this->progid($out)) { + return true; + } + + if (($char === '"' || $char === "'") && $this->string($out)) { + return true; + } + + if ($this->unit($out)) { + return true; + } + + // unicode range with wildcards + if ( + $this->literal('U+', 2) && + $this->match('\?+|([0-9A-F]+(\?+|(-[0-9A-F]+))?)', $m, false) + ) { + $unicode = explode('-', $m[0]); + if (strlen(reset($unicode)) <= 6 && strlen(end($unicode)) <= 6) { + $out = [Type::T_KEYWORD, 'U+' . $m[0]]; + + return true; + } + $this->count -= strlen($m[0]) + 2; + } + + if ($this->keyword($keyword, false)) { + if ($this->func($keyword, $out)) { + return true; + } + + $this->whitespace(); + + if ($keyword === 'null') { + $out = [Type::T_NULL]; + } else { + $out = [Type::T_KEYWORD, $keyword]; + } + + return true; + } + + return false; + } + + /** + * Parse parenthesized value + * + * @param array $out + * + * @return boolean + */ + protected function parenValue(&$out) + { + $s = $this->count; + + $inParens = $this->inParens; + + if ($this->matchChar('(')) { + if ($this->matchChar(')')) { + $out = [Type::T_LIST, '', []]; + + return true; + } + + $this->inParens = true; + + if ( + $this->expression($exp) && + $this->matchChar(')') + ) { + $out = $exp; + $this->inParens = $inParens; + + return true; + } + } + + $this->inParens = $inParens; + $this->seek($s); + + return false; + } + + /** + * Parse "progid:" + * + * @param array $out + * + * @return boolean + */ + protected function progid(&$out) + { + $s = $this->count; + + if ( + $this->literal('progid:', 7, false) && + $this->openString('(', $fn) && + $this->matchChar('(') + ) { + $this->openString(')', $args, '('); + + if ($this->matchChar(')')) { + $out = [Type::T_STRING, '', [ + 'progid:', $fn, '(', $args, ')' + ]]; + + return true; + } + } + + $this->seek($s); + + return false; + } + + /** + * Parse function call + * + * @param string $name + * @param array $func + * + * @return boolean + */ + protected function func($name, &$func) + { + $s = $this->count; + + if ($this->matchChar('(')) { + if ($name === 'alpha' && $this->argumentList($args)) { + $func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]]; + + return true; + } + + if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) { + $ss = $this->count; + + if ( + $this->argValues($args) && + $this->matchChar(')') + ) { + $func = [Type::T_FUNCTION_CALL, $name, $args]; + + return true; + } + + $this->seek($ss); + } + + if ( + ($this->openString(')', $str, '(') || true) && + $this->matchChar(')') + ) { + $args = []; + + if (! empty($str)) { + $args[] = [null, [Type::T_STRING, '', [$str]]]; + } + + $func = [Type::T_FUNCTION_CALL, $name, $args]; + + return true; + } + } + + $this->seek($s); + + return false; + } + + /** + * Parse function call argument list + * + * @param array $out + * + * @return boolean + */ + protected function argumentList(&$out) + { + $s = $this->count; + $this->matchChar('('); + + $args = []; + + while ($this->keyword($var)) { + if ( + $this->matchChar('=') && + $this->expression($exp) + ) { + $args[] = [Type::T_STRING, '', [$var . '=']]; + $arg = $exp; + } else { + break; + } + + $args[] = $arg; + + if (! $this->matchChar(',')) { + break; + } + + $args[] = [Type::T_STRING, '', [', ']]; + } + + if (! $this->matchChar(')') || ! $args) { + $this->seek($s); + + return false; + } + + $out = $args; + + return true; + } + + /** + * Parse mixin/function definition argument list + * + * @param array $out + * + * @return boolean + */ + protected function argumentDef(&$out) + { + $s = $this->count; + $this->matchChar('('); + + $args = []; + + while ($this->variable($var)) { + $arg = [$var[1], null, false]; + + $ss = $this->count; + + if ( + $this->matchChar(':') && + $this->genericList($defaultVal, 'expression', '', true) + ) { + $arg[1] = $defaultVal; + } else { + $this->seek($ss); + } + + $ss = $this->count; + + if ($this->literal('...', 3)) { + $sss = $this->count; + + if (! $this->matchChar(')')) { + throw $this->parseError('... has to be after the final argument'); + } + + $arg[2] = true; + + $this->seek($sss); + } else { + $this->seek($ss); + } + + $args[] = $arg; + + if (! $this->matchChar(',')) { + break; + } + } + + if (! $this->matchChar(')')) { + $this->seek($s); + + return false; + } + + $out = $args; + + return true; + } + + /** + * Parse map + * + * @param array $out + * + * @return boolean + */ + protected function map(&$out) + { + $s = $this->count; + + if (! $this->matchChar('(')) { + return false; + } + + $keys = []; + $values = []; + + while ( + $this->genericList($key, 'expression', '', true) && + $this->matchChar(':') && + $this->genericList($value, 'expression', '', true) + ) { + $keys[] = $key; + $values[] = $value; + + if (! $this->matchChar(',')) { + break; + } + } + + if (! $keys || ! $this->matchChar(')')) { + $this->seek($s); + + return false; + } + + $out = [Type::T_MAP, $keys, $values]; + + return true; + } + + /** + * Parse color + * + * @param array $out + * + * @return boolean + */ + protected function color(&$out) + { + $s = $this->count; + + if ($this->match('(#([0-9a-f]+)\b)', $m)) { + if (\in_array(\strlen($m[2]), [3,4,6,8])) { + $out = [Type::T_KEYWORD, $m[0]]; + + return true; + } + + $this->seek($s); + + return false; + } + + return false; + } + + /** + * Parse number with unit + * + * @param array $unit + * + * @return boolean + */ + protected function unit(&$unit) + { + $s = $this->count; + + if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m, false)) { + if (\strlen($this->buffer) === $this->count || ! ctype_digit($this->buffer[$this->count])) { + $this->whitespace(); + + $unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]); + + return true; + } + + $this->seek($s); + } + + return false; + } + + /** + * Parse string + * + * @param array $out + * + * @return boolean + */ + protected function string(&$out, $keepDelimWithInterpolation = false) + { + $s = $this->count; + + if ($this->matchChar('"', false)) { + $delim = '"'; + } elseif ($this->matchChar("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = []; + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + $hasInterpolation = false; + + while ($this->matchString($m, $delim)) { + if ($m[1] !== '') { + $content[] = $m[1]; + } + + if ($m[2] === '#{') { + $this->count -= \strlen($m[2]); + + if ($this->interpolation($inter, false)) { + $content[] = $inter; + $hasInterpolation = true; + } else { + $this->count += \strlen($m[2]); + $content[] = '#{'; // ignore it + } + } elseif ($m[2] === "\r") { + $content[] = chr(10); + // TODO : warning + # DEPRECATION WARNING on line x, column y of zzz: + # Unescaped multiline strings are deprecated and will be removed in a future version of Sass. + # To include a newline in a string, use "\a" or "\a " as in CSS. + if ($this->matchChar("\n", false)) { + $content[] = ' '; + } + } elseif ($m[2] === '\\') { + if ( + $this->literal("\r\n", 2, false) || + $this->matchChar("\r", false) || + $this->matchChar("\n", false) || + $this->matchChar("\f", false) + ) { + // this is a continuation escaping, to be ignored + } elseif ($this->matchEscapeCharacter($c)) { + $content[] = $c; + } else { + throw $this->parseError('Unterminated escape sequence'); + } + } else { + $this->count -= \strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim, \strlen($delim))) { + if ($hasInterpolation && ! $keepDelimWithInterpolation) { + $delim = '"'; + } + + $out = [Type::T_STRING, $delim, $content]; + + return true; + } + + $this->seek($s); + + return false; + } + + /** + * @param string $out + * @param bool $inKeywords + * @return bool + */ + protected function matchEscapeCharacter(&$out, $inKeywords = false) + { + $s = $this->count; + if ($this->match('[a-f0-9]', $m, false)) { + $hex = $m[0]; + + for ($i = 5; $i--;) { + if ($this->match('[a-f0-9]', $m, false)) { + $hex .= $m[0]; + } else { + break; + } + } + + // CSS allows Unicode escape sequences to be followed by a delimiter space + // (necessary in some cases for shorter sequences to disambiguate their end) + $this->matchChar(' ', false); + + $value = hexdec($hex); + + if (!$inKeywords && ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF)) { + $out = "\xEF\xBF\xBD"; // "\u{FFFD}" but with a syntax supported on PHP 5 + } elseif ($value < 0x20) { + $out = Util::mbChr($value); + } else { + $out = Util::mbChr($value); + } + + return true; + } + + if ($this->match('.', $m, false)) { + if ($inKeywords && in_array($m[0], ["'",'"','@','&',' ','\\',':','/','%'])) { + $this->seek($s); + return false; + } + $out = $m[0]; + + return true; + } + + return false; + } + + /** + * Parse keyword or interpolation + * + * @param array $out + * @param boolean $restricted + * + * @return boolean + */ + protected function mixedKeyword(&$out, $restricted = false) + { + $parts = []; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + for (;;) { + if ($restricted ? $this->restrictedKeyword($key) : $this->keyword($key)) { + $parts[] = $key; + continue; + } + + if ($this->interpolation($inter)) { + $parts[] = $inter; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (! $parts) { + return false; + } + + if ($this->eatWhiteDefault) { + $this->whitespace(); + } + + $out = $parts; + + return true; + } + + /** + * Parse an unbounded string stopped by $end + * + * @param string $end + * @param array $out + * @param string $nestOpen + * @param string $nestClose + * @param boolean $rtrim + * @param string $disallow + * + * @return boolean + */ + protected function openString($end, &$out, $nestOpen = null, $nestClose = null, $rtrim = true, $disallow = null) + { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + if ($nestOpen && ! $nestClose) { + $nestClose = $end; + } + + $patt = ($disallow ? '[^' . $this->pregQuote($disallow) . ']' : '.'); + $patt = '(' . $patt . '*?)([\'"]|#\{|' + . $this->pregQuote($end) . '|' + . (($nestClose && $nestClose !== $end) ? $this->pregQuote($nestClose) . '|' : '') + . static::$commentPattern . ')'; + + $nestingLevel = 0; + + $content = []; + + while ($this->match($patt, $m, false)) { + if (isset($m[1]) && $m[1] !== '') { + $content[] = $m[1]; + + if ($nestOpen) { + $nestingLevel += substr_count($m[1], $nestOpen); + } + } + + $tok = $m[2]; + + $this->count -= \strlen($tok); + + if ($tok === $end && ! $nestingLevel) { + break; + } + + if ($tok === $nestClose) { + $nestingLevel--; + } + + if (($tok === "'" || $tok === '"') && $this->string($str, true)) { + $content[] = $str; + continue; + } + + if ($tok === '#{' && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + $content[] = $tok; + $this->count += \strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (! $content || $tok !== $end) { + return false; + } + + // trim the end + if ($rtrim && \is_string(end($content))) { + $content[\count($content) - 1] = rtrim(end($content)); + } + + $out = [Type::T_STRING, '', $content]; + + return true; + } + + /** + * Parser interpolation + * + * @param string|array $out + * @param boolean $lookWhite save information about whitespace before and after + * + * @return boolean + */ + protected function interpolation(&$out, $lookWhite = true) + { + $oldWhite = $this->eatWhiteDefault; + $allowVars = $this->allowVars; + $this->allowVars = true; + $this->eatWhiteDefault = true; + + $s = $this->count; + + if ( + $this->literal('#{', 2) && + $this->valueList($value) && + $this->matchChar('}', false) + ) { + if ($value === [Type::T_SELF]) { + $out = $value; + } else { + if ($lookWhite) { + $left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : ''; + $right = ( + ! empty($this->buffer[$this->count]) && + preg_match('/\s/', $this->buffer[$this->count]) + ) ? ' ' : ''; + } else { + $left = $right = false; + } + + $out = [Type::T_INTERPOLATE, $value, $left, $right]; + } + + $this->eatWhiteDefault = $oldWhite; + $this->allowVars = $allowVars; + + if ($this->eatWhiteDefault) { + $this->whitespace(); + } + + return true; + } + + $this->seek($s); + + $this->eatWhiteDefault = $oldWhite; + $this->allowVars = $allowVars; + + return false; + } + + /** + * Parse property name (as an array of parts or a string) + * + * @param array $out + * + * @return boolean + */ + protected function propertyName(&$out) + { + $parts = []; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + for (;;) { + if ($this->interpolation($inter)) { + $parts[] = $inter; + continue; + } + + if ($this->keyword($text)) { + $parts[] = $text; + continue; + } + + if (! $parts && $this->match('[:.#]', $m, false)) { + // css hacks + $parts[] = $m[0]; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (! $parts) { + return false; + } + + // match comment hack + if (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) { + if (! empty($m[0])) { + $parts[] = $m[0]; + $this->count += \strlen($m[0]); + } + } + + $this->whitespace(); // get any extra whitespace + + $out = [Type::T_STRING, '', $parts]; + + return true; + } + + /** + * Parse custom property name (as an array of parts or a string) + * + * @param array $out + * + * @return boolean + */ + protected function customProperty(&$out) + { + $s = $this->count; + + if (! $this->literal('--', 2, false)) { + return false; + } + + $parts = ['--']; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + for (;;) { + if ($this->interpolation($inter)) { + $parts[] = $inter; + continue; + } + + if ($this->matchChar('&', false)) { + $parts[] = [Type::T_SELF]; + continue; + } + + if ($this->variable($var)) { + $parts[] = $var; + continue; + } + + if ($this->keyword($text)) { + $parts[] = $text; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (\count($parts) == 1) { + $this->seek($s); + + return false; + } + + $this->whitespace(); // get any extra whitespace + + $out = [Type::T_STRING, '', $parts]; + + return true; + } + + /** + * Parse comma separated selector list + * + * @param array $out + * @param string|boolean $subSelector + * + * @return boolean + */ + protected function selectors(&$out, $subSelector = false) + { + $s = $this->count; + $selectors = []; + + while ($this->selector($sel, $subSelector)) { + $selectors[] = $sel; + + if (! $this->matchChar(',', true)) { + break; + } + + while ($this->matchChar(',', true)) { + ; // ignore extra + } + } + + if (! $selectors) { + $this->seek($s); + + return false; + } + + $out = $selectors; + + return true; + } + + /** + * Parse whitespace separated selector list + * + * @param array $out + * @param string|boolean $subSelector + * + * @return boolean + */ + protected function selector(&$out, $subSelector = false) + { + $selector = []; + + $discardComments = $this->discardComments; + $this->discardComments = true; + + for (;;) { + $s = $this->count; + + if ($this->match('[>+~]+', $m, true)) { + if ( + $subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 && + $m[0] === '+' && $this->match("(\d+|n\b)", $counter) + ) { + $this->seek($s); + } else { + $selector[] = [$m[0]]; + continue; + } + } + + if ($this->selectorSingle($part, $subSelector)) { + $selector[] = $part; + $this->whitespace(); + continue; + } + + break; + } + + $this->discardComments = $discardComments; + + if (! $selector) { + return false; + } + + $out = $selector; + + return true; + } + + /** + * parsing escaped chars in selectors: + * - escaped single chars are kept escaped in the selector but in a normalized form + * (if not in 0-9a-f range as this would be ambigous) + * - other escaped sequences (multibyte chars or 0-9a-f) are kept in their initial escaped form, + * normalized to lowercase + * + * TODO: this is a fallback solution. Ideally escaped chars in selectors should be encoded as the genuine chars, + * and escaping added when printing in the Compiler, where/if it's mandatory + * - but this require a better formal selector representation instead of the array we have now + * + * @param string $out + * @param bool $keepEscapedNumber + * @return bool + */ + protected function matchEscapeCharacterInSelector(&$out, $keepEscapedNumber = false) + { + $s_escape = $this->count; + if ($this->match('\\\\', $m)) { + $out = '\\' . $m[0]; + return true; + } + + if ($this->matchEscapeCharacter($escapedout, true)) { + if (strlen($escapedout) === 1) { + if (!preg_match(",\w,", $escapedout)) { + $out = '\\' . $escapedout; + return true; + } elseif (! $keepEscapedNumber || ! \is_numeric($escapedout)) { + $out = $escapedout; + return true; + } + } + $escape_sequence = rtrim(substr($this->buffer, $s_escape, $this->count - $s_escape)); + if (strlen($escape_sequence) < 6) { + $escape_sequence .= ' '; + } + $out = '\\' . strtolower($escape_sequence); + return true; + } + if ($this->match('\\S', $m)) { + $out = '\\' . $m[0]; + return true; + } + + + return false; + } + + /** + * Parse the parts that make up a selector + * + * {@internal + * div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder + * }} + * + * @param array $out + * @param string|boolean $subSelector + * + * @return boolean + */ + protected function selectorSingle(&$out, $subSelector = false) + { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $parts = []; + + if ($this->matchChar('*', false)) { + $parts[] = '*'; + } + + for (;;) { + if (! isset($this->buffer[$this->count])) { + break; + } + + $s = $this->count; + $char = $this->buffer[$this->count]; + + // see if we can stop early + if ($char === '{' || $char === ',' || $char === ';' || $char === '}' || $char === '@') { + break; + } + + // parsing a sub selector in () stop with the closing ) + if ($subSelector && $char === ')') { + break; + } + + //self + switch ($char) { + case '&': + $parts[] = Compiler::$selfSelector; + $this->count++; + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + continue 2; + + case '.': + $parts[] = '.'; + $this->count++; + continue 2; + + case '|': + $parts[] = '|'; + $this->count++; + continue 2; + } + + // handling of escaping in selectors : get the escaped char + if ($char === '\\') { + $this->count++; + if ($this->matchEscapeCharacterInSelector($escaped, true)) { + $parts[] = $escaped; + continue; + } + $this->count--; + } + + if ($char === '%') { + $this->count++; + + if ($this->placeholder($placeholder)) { + $parts[] = '%'; + $parts[] = $placeholder; + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + continue; + } + + break; + } + + if ($char === '#') { + if ($this->interpolation($inter)) { + $parts[] = $inter; + ! $this->cssOnly || $this->assertPlainCssValid(false, $s); + continue; + } + + $parts[] = '#'; + $this->count++; + continue; + } + + // a pseudo selector + if ($char === ':') { + if ($this->buffer[$this->count + 1] === ':') { + $this->count += 2; + $part = '::'; + } else { + $this->count++; + $part = ':'; + } + + if ($this->mixedKeyword($nameParts, true)) { + $parts[] = $part; + + foreach ($nameParts as $sub) { + $parts[] = $sub; + } + + $ss = $this->count; + + if ( + $nameParts === ['not'] || + $nameParts === ['is'] || + $nameParts === ['has'] || + $nameParts === ['where'] || + $nameParts === ['slotted'] || + $nameParts === ['nth-child'] || + $nameParts === ['nth-last-child'] || + $nameParts === ['nth-of-type'] || + $nameParts === ['nth-last-of-type'] + ) { + if ( + $this->matchChar('(', true) && + ($this->selectors($subs, reset($nameParts)) || true) && + $this->matchChar(')') + ) { + $parts[] = '('; + + while ($sub = array_shift($subs)) { + while ($ps = array_shift($sub)) { + foreach ($ps as &$p) { + $parts[] = $p; + } + + if (\count($sub) && reset($sub)) { + $parts[] = ' '; + } + } + + if (\count($subs) && reset($subs)) { + $parts[] = ', '; + } + } + + $parts[] = ')'; + } else { + $this->seek($ss); + } + } elseif ( + $this->matchChar('(', true) && + ($this->openString(')', $str, '(') || true) && + $this->matchChar(')') + ) { + $parts[] = '('; + + if (! empty($str)) { + $parts[] = $str; + } + + $parts[] = ')'; + } else { + $this->seek($ss); + } + + continue; + } + } + + $this->seek($s); + + // 2n+1 + if ($subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0) { + if ($this->match("(\s*(\+\s*|\-\s*)?(\d+|n|\d+n))+", $counter)) { + $parts[] = $counter[0]; + //$parts[] = str_replace(' ', '', $counter[0]); + continue; + } + } + + $this->seek($s); + + // attribute selector + if ( + $char === '[' && + $this->matchChar('[') && + ($this->openString(']', $str, '[') || true) && + $this->matchChar(']') + ) { + $parts[] = '['; + + if (! empty($str)) { + $parts[] = $str; + } + + $parts[] = ']'; + continue; + } + + $this->seek($s); + + // for keyframes + if ($this->unit($unit)) { + $parts[] = $unit; + continue; + } + + if ($this->restrictedKeyword($name, false, true)) { + $parts[] = $name; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (! $parts) { + return false; + } + + $out = $parts; + + return true; + } + + /** + * Parse a variable + * + * @param array $out + * + * @return boolean + */ + protected function variable(&$out) + { + $s = $this->count; + + if ( + $this->matchChar('$', false) && + $this->keyword($name) + ) { + if ($this->allowVars) { + $out = [Type::T_VARIABLE, $name]; + } else { + $out = [Type::T_KEYWORD, '$' . $name]; + } + + return true; + } + + $this->seek($s); + + return false; + } + + /** + * Parse a keyword + * + * @param string $word + * @param boolean $eatWhitespace + * @param boolean $inSelector + * + * @return boolean + */ + protected function keyword(&$word, $eatWhitespace = null, $inSelector = false) + { + $s = $this->count; + $match = $this->match( + $this->utf8 + ? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)*)' + : '(([\w_\-\*!"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)([\w\-_"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)*)', + $m, + false + ); + + if ($match) { + $word = $m[1]; + + // handling of escaping in keyword : get the escaped char + if (strpos($word, '\\') !== false) { + $send = $this->count; + $escapedWord = []; + $this->seek($s); + $previousEscape = false; + while ($this->count < $send) { + $char = $this->buffer[$this->count]; + $this->count++; + if ( + $this->count < $send + && $char === '\\' + && !$previousEscape + && ( + $inSelector ? + $this->matchEscapeCharacterInSelector($out) + : + $this->matchEscapeCharacter($out, true) + ) + ) { + $escapedWord[] = $out; + } else { + if ($previousEscape) { + $previousEscape = false; + } elseif ($char === '\\') { + $previousEscape = true; + } + $escapedWord[] = $char; + } + } + + $word = implode('', $escapedWord); + } + + if (is_null($eatWhitespace) ? $this->eatWhiteDefault : $eatWhitespace) { + $this->whitespace(); + } + + return true; + } + + return false; + } + + /** + * Parse a keyword that should not start with a number + * + * @param string $word + * @param boolean $eatWhitespace + * @param boolean $inSelector + * + * @return boolean + */ + protected function restrictedKeyword(&$word, $eatWhitespace = null, $inSelector = false) + { + $s = $this->count; + + if ($this->keyword($word, $eatWhitespace, $inSelector) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) { + return true; + } + + $this->seek($s); + + return false; + } + + /** + * Parse a placeholder + * + * @param string|array $placeholder + * + * @return boolean + */ + protected function placeholder(&$placeholder) + { + $match = $this->match( + $this->utf8 + ? '([\pL\w\-_]+)' + : '([\w\-_]+)', + $m + ); + + if ($match) { + $placeholder = $m[1]; + + return true; + } + + if ($this->interpolation($placeholder)) { + return true; + } + + return false; + } + + /** + * Parse a url + * + * @param array $out + * + * @return boolean + */ + protected function url(&$out) + { + if ($this->literal('url(', 4)) { + $s = $this->count; + + if ( + ($this->string($out) || $this->spaceList($out)) && + $this->matchChar(')') + ) { + $out = [Type::T_STRING, '', ['url(', $out, ')']]; + + return true; + } + + $this->seek($s); + + if ( + $this->openString(')', $out) && + $this->matchChar(')') + ) { + $out = [Type::T_STRING, '', ['url(', $out, ')']]; + + return true; + } + } + + return false; + } + + /** + * Consume an end of statement delimiter + * @param bool $eatWhitespace + * + * @return boolean + */ + protected function end($eatWhitespace = null) + { + if ($this->matchChar(';', $eatWhitespace)) { + return true; + } + + if ($this->count === \strlen($this->buffer) || $this->buffer[$this->count] === '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + + return false; + } + + /** + * Strip assignment flag from the list + * + * @param array $value + * + * @return array + */ + protected function stripAssignmentFlags(&$value) + { + $flags = []; + + for ($token = &$value; $token[0] === Type::T_LIST && ($s = \count($token[2])); $token = &$lastNode) { + $lastNode = &$token[2][$s - 1]; + + while ($lastNode[0] === Type::T_KEYWORD && \in_array($lastNode[1], ['!default', '!global'])) { + array_pop($token[2]); + + $node = end($token[2]); + $token = $this->flattenList($token); + $flags[] = $lastNode[1]; + $lastNode = $node; + } + } + + return $flags; + } + + /** + * Strip optional flag from selector list + * + * @param array $selectors + * + * @return string + */ + protected function stripOptionalFlag(&$selectors) + { + $optional = false; + $selector = end($selectors); + $part = end($selector); + + if ($part === ['!optional']) { + array_pop($selectors[\count($selectors) - 1]); + + $optional = true; + } + + return $optional; + } + + /** + * Turn list of length 1 into value type + * + * @param array $value + * + * @return array + */ + protected function flattenList($value) + { + if ($value[0] === Type::T_LIST && \count($value[2]) === 1) { + return $this->flattenList($value[2][0]); + } + + return $value; + } + + /** + * Quote regular expression + * + * @param string $what + * + * @return string + */ + private function pregQuote($what) + { + return preg_quote($what, '/'); + } + + /** + * Extract line numbers from buffer + * + * @param string $buffer + */ + private function extractLineNumbers($buffer) + { + $this->sourcePositions = [0 => 0]; + $prev = 0; + + while (($pos = strpos($buffer, "\n", $prev)) !== false) { + $this->sourcePositions[] = $pos; + $prev = $pos + 1; + } + + $this->sourcePositions[] = \strlen($buffer); + + if (substr($buffer, -1) !== "\n") { + $this->sourcePositions[] = \strlen($buffer) + 1; + } + } + + /** + * Get source line number and column (given character position in the buffer) + * + * @param integer $pos + * + * @return array + */ + private function getSourcePosition($pos) + { + $low = 0; + $high = \count($this->sourcePositions); + + while ($low < $high) { + $mid = (int) (($high + $low) / 2); + + if ($pos < $this->sourcePositions[$mid]) { + $high = $mid - 1; + continue; + } + + if ($pos >= $this->sourcePositions[$mid + 1]) { + $low = $mid + 1; + continue; + } + + return [$mid + 1, $pos - $this->sourcePositions[$mid]]; + } + + return [$low + 1, $pos - $this->sourcePositions[$low]]; + } + + /** + * Save internal encoding of mbstring + * + * When mbstring.func_overload is used to replace the standard PHP string functions, + * this method configures the internal encoding to a single-byte one so that the + * behavior matches the normal behavior of PHP string functions while using the parser. + * The existing internal encoding is saved and will be restored when calling {@see restoreEncoding}. + * + * If mbstring.func_overload is not used (or does not override string functions), this method is a no-op. + * + * @return void + */ + private function saveEncoding() + { + if (\PHP_VERSION_ID < 80000 && \extension_loaded('mbstring') && (2 & (int) ini_get('mbstring.func_overload')) > 0) { + $this->encoding = mb_internal_encoding(); + + mb_internal_encoding('iso-8859-1'); + } + } + + /** + * Restore internal encoding + * + * @return void + */ + private function restoreEncoding() + { + if (\extension_loaded('mbstring') && $this->encoding) { + mb_internal_encoding($this->encoding); + } + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64.php b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64.php new file mode 100644 index 0000000..4a5ed8b --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64.php @@ -0,0 +1,187 @@ + + * + * @internal + */ +class Base64 +{ + /** + * @var array + */ + private static $encodingMap = [ + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + 4 => 'E', + 5 => 'F', + 6 => 'G', + 7 => 'H', + 8 => 'I', + 9 => 'J', + 10 => 'K', + 11 => 'L', + 12 => 'M', + 13 => 'N', + 14 => 'O', + 15 => 'P', + 16 => 'Q', + 17 => 'R', + 18 => 'S', + 19 => 'T', + 20 => 'U', + 21 => 'V', + 22 => 'W', + 23 => 'X', + 24 => 'Y', + 25 => 'Z', + 26 => 'a', + 27 => 'b', + 28 => 'c', + 29 => 'd', + 30 => 'e', + 31 => 'f', + 32 => 'g', + 33 => 'h', + 34 => 'i', + 35 => 'j', + 36 => 'k', + 37 => 'l', + 38 => 'm', + 39 => 'n', + 40 => 'o', + 41 => 'p', + 42 => 'q', + 43 => 'r', + 44 => 's', + 45 => 't', + 46 => 'u', + 47 => 'v', + 48 => 'w', + 49 => 'x', + 50 => 'y', + 51 => 'z', + 52 => '0', + 53 => '1', + 54 => '2', + 55 => '3', + 56 => '4', + 57 => '5', + 58 => '6', + 59 => '7', + 60 => '8', + 61 => '9', + 62 => '+', + 63 => '/', + ]; + + /** + * @var array + */ + private static $decodingMap = [ + 'A' => 0, + 'B' => 1, + 'C' => 2, + 'D' => 3, + 'E' => 4, + 'F' => 5, + 'G' => 6, + 'H' => 7, + 'I' => 8, + 'J' => 9, + 'K' => 10, + 'L' => 11, + 'M' => 12, + 'N' => 13, + 'O' => 14, + 'P' => 15, + 'Q' => 16, + 'R' => 17, + 'S' => 18, + 'T' => 19, + 'U' => 20, + 'V' => 21, + 'W' => 22, + 'X' => 23, + 'Y' => 24, + 'Z' => 25, + 'a' => 26, + 'b' => 27, + 'c' => 28, + 'd' => 29, + 'e' => 30, + 'f' => 31, + 'g' => 32, + 'h' => 33, + 'i' => 34, + 'j' => 35, + 'k' => 36, + 'l' => 37, + 'm' => 38, + 'n' => 39, + 'o' => 40, + 'p' => 41, + 'q' => 42, + 'r' => 43, + 's' => 44, + 't' => 45, + 'u' => 46, + 'v' => 47, + 'w' => 48, + 'x' => 49, + 'y' => 50, + 'z' => 51, + 0 => 52, + 1 => 53, + 2 => 54, + 3 => 55, + 4 => 56, + 5 => 57, + 6 => 58, + 7 => 59, + 8 => 60, + 9 => 61, + '+' => 62, + '/' => 63, + ]; + + /** + * Convert to base64 + * + * @param integer $value + * + * @return string + */ + public static function encode($value) + { + return self::$encodingMap[$value]; + } + + /** + * Convert from base64 + * + * @param string $value + * + * @return integer + */ + public static function decode($value) + { + return self::$decodingMap[$value]; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64VLQ.php b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64VLQ.php new file mode 100644 index 0000000..d47b96a --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/Base64VLQ.php @@ -0,0 +1,151 @@ + + * @author Anthon Pang + * + * @internal + */ +class Base64VLQ +{ + // A Base64 VLQ digit can represent 5 bits, so it is base-32. + const VLQ_BASE_SHIFT = 5; + + // A mask of bits for a VLQ digit (11111), 31 decimal. + const VLQ_BASE_MASK = 31; + + // The continuation bit is the 6th bit. + const VLQ_CONTINUATION_BIT = 32; + + /** + * Returns the VLQ encoded value. + * + * @param integer $value + * + * @return string + */ + public static function encode($value) + { + $encoded = ''; + $vlq = self::toVLQSigned($value); + + do { + $digit = $vlq & self::VLQ_BASE_MASK; + + //$vlq >>>= self::VLQ_BASE_SHIFT; // unsigned right shift + $vlq = (($vlq >> 1) & PHP_INT_MAX) >> (self::VLQ_BASE_SHIFT - 1); + + if ($vlq > 0) { + $digit |= self::VLQ_CONTINUATION_BIT; + } + + $encoded .= Base64::encode($digit); + } while ($vlq > 0); + + return $encoded; + } + + /** + * Decodes VLQValue. + * + * @param string $str + * @param integer $index + * + * @return integer + */ + public static function decode($str, &$index) + { + $result = 0; + $shift = 0; + + do { + $c = $str[$index++]; + $digit = Base64::decode($c); + $continuation = ($digit & self::VLQ_CONTINUATION_BIT) != 0; + $digit &= self::VLQ_BASE_MASK; + $result = $result + ($digit << $shift); + $shift = $shift + self::VLQ_BASE_SHIFT; + } while ($continuation); + + return self::fromVLQSigned($result); + } + + /** + * Converts from a two-complement value to a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + * + * @param integer $value + * + * @return integer + */ + private static function toVLQSigned($value) + { + if ($value < 0) { + return ((-$value) << 1) + 1; + } + + return ($value << 1) + 0; + } + + /** + * Converts to a two-complement value from a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + * + * @param integer $value + * + * @return integer + */ + private static function fromVLQSigned($value) + { + $negate = ($value & 1) === 1; + + //$value >>>= 1; // unsigned right shift + $value = ($value >> 1) & PHP_INT_MAX; + + if (! $negate) { + return $value; + } + + // We need to OR 0x80000000 here to ensure the 32nd bit (the sign bit) is + // always set for negative numbers. If `value` were 1, (meaning `negate` is + // true and all other bits were zeros), `value` would now be 0. -0 is just + // 0, and doesn't flip the 32nd bit as intended. All positive numbers will + // successfully flip the 32nd bit without issue, so it's a noop for them. + return -$value | 0x80000000; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php new file mode 100644 index 0000000..4f14bdc --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php @@ -0,0 +1,381 @@ + + * @author Nicolas FRANÇOIS + * + * @internal + */ +class SourceMapGenerator +{ + /** + * What version of source map does the generator generate? + */ + const VERSION = 3; + + /** + * Array of default options + * + * @var array + * @phpstan-var array{sourceRoot: string, sourceMapFilename: string|null, sourceMapURL: string|null, sourceMapWriteTo: string|null, outputSourceFiles: bool, sourceMapRootpath: string, sourceMapBasepath: string} + */ + protected $defaultOptions = [ + // an optional source root, useful for relocating source files + // on a server or removing repeated values in the 'sources' entry. + // This value is prepended to the individual entries in the 'source' field. + 'sourceRoot' => '', + + // an optional name of the generated code that this source map is associated with. + 'sourceMapFilename' => null, + + // url of the map + 'sourceMapURL' => null, + + // absolute path to a file to write the map to + 'sourceMapWriteTo' => null, + + // output source contents? + 'outputSourceFiles' => false, + + // base path for filename normalization + 'sourceMapRootpath' => '', + + // base path for filename normalization + 'sourceMapBasepath' => '' + ]; + + /** + * The base64 VLQ encoder + * + * @var \ScssPhp\ScssPhp\SourceMap\Base64VLQ + */ + protected $encoder; + + /** + * Array of mappings + * + * @var array + * @phpstan-var list + */ + protected $mappings = []; + + /** + * Array of contents map + * + * @var array + */ + protected $contentsMap = []; + + /** + * File to content map + * + * @var array + */ + protected $sources = []; + + /** + * @var array + */ + protected $sourceKeys = []; + + /** + * @var array + * @phpstan-var array{sourceRoot: string, sourceMapFilename: string|null, sourceMapURL: string|null, sourceMapWriteTo: string|null, outputSourceFiles: bool, sourceMapRootpath: string, sourceMapBasepath: string} + */ + private $options; + + /** + * @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $options + */ + public function __construct(array $options = []) + { + $this->options = array_merge($this->defaultOptions, $options); + $this->encoder = new Base64VLQ(); + } + + /** + * Adds a mapping + * + * @param integer $generatedLine The line number in generated file + * @param integer $generatedColumn The column number in generated file + * @param integer $originalLine The line number in original file + * @param integer $originalColumn The column number in original file + * @param string $sourceFile The original source file + * + * @return void + */ + public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile) + { + $this->mappings[] = [ + 'generated_line' => $generatedLine, + 'generated_column' => $generatedColumn, + 'original_line' => $originalLine, + 'original_column' => $originalColumn, + 'source_file' => $sourceFile + ]; + + $this->sources[$sourceFile] = $sourceFile; + } + + /** + * Saves the source map to a file + * + * @param string $content The content to write + * + * @return string + * + * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved + * @deprecated + */ + public function saveMap($content) + { + $file = $this->options['sourceMapWriteTo']; + $dir = \dirname($file); + + // directory does not exist + if (! is_dir($dir)) { + // FIXME: create the dir automatically? + throw new CompilerException( + sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir) + ); + } + + // FIXME: proper saving, with dir write check! + if (file_put_contents($file, $content) === false) { + throw new CompilerException(sprintf('Cannot save the source map to "%s"', $file)); + } + + return $this->options['sourceMapURL']; + } + + /** + * Generates the JSON source map + * + * @param string $prefix A prefix added in the output file, which needs to shift mappings + * + * @return string + * + * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# + */ + public function generateJson($prefix = '') + { + $sourceMap = []; + $mappings = $this->generateMappings($prefix); + + // File version (always the first entry in the object) and must be a positive integer. + $sourceMap['version'] = self::VERSION; + + // An optional name of the generated code that this source map is associated with. + $file = $this->options['sourceMapFilename']; + + if ($file) { + $sourceMap['file'] = $file; + } + + // An optional source root, useful for relocating source files on a server or removing repeated values in the + // 'sources' entry. This value is prepended to the individual entries in the 'source' field. + $root = $this->options['sourceRoot']; + + if ($root) { + $sourceMap['sourceRoot'] = $root; + } + + // A list of original sources used by the 'mappings' entry. + $sourceMap['sources'] = []; + + foreach ($this->sources as $sourceUri => $sourceFilename) { + $sourceMap['sources'][] = $this->normalizeFilename($sourceFilename); + } + + // A list of symbol names used by the 'mappings' entry. + $sourceMap['names'] = []; + + // A string with the encoded mapping data. + $sourceMap['mappings'] = $mappings; + + if ($this->options['outputSourceFiles']) { + // An optional list of source content, useful when the 'source' can't be hosted. + // The contents are listed in the same order as the sources above. + // 'null' may be used if some original sources should be retrieved by name. + $sourceMap['sourcesContent'] = $this->getSourcesContent(); + } + + // less.js compat fixes + if (\count($sourceMap['sources']) && empty($sourceMap['sourceRoot'])) { + unset($sourceMap['sourceRoot']); + } + + return json_encode($sourceMap, JSON_UNESCAPED_SLASHES); + } + + /** + * Returns the sources contents + * + * @return string[]|null + */ + protected function getSourcesContent() + { + if (empty($this->sources)) { + return null; + } + + $content = []; + + foreach ($this->sources as $sourceFile) { + $content[] = file_get_contents($sourceFile); + } + + return $content; + } + + /** + * Generates the mappings string + * + * @param string $prefix A prefix added in the output file, which needs to shift mappings + * + * @return string + */ + public function generateMappings($prefix = '') + { + if (! \count($this->mappings)) { + return ''; + } + + $prefixLines = substr_count($prefix, "\n"); + $lastPrefixNewLine = strrpos($prefix, "\n"); + $lastPrefixLineStart = false === $lastPrefixNewLine ? 0 : $lastPrefixNewLine + 1; + $prefixColumn = strlen($prefix) - $lastPrefixLineStart; + + $this->sourceKeys = array_flip(array_keys($this->sources)); + + // group mappings by generated line number. + $groupedMap = $groupedMapEncoded = []; + + foreach ($this->mappings as $m) { + $groupedMap[$m['generated_line']][] = $m; + } + + ksort($groupedMap); + + $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; + + foreach ($groupedMap as $lineNumber => $lineMap) { + if ($lineNumber > 1) { + // The prefix only impacts the column for the first line of the original output + $prefixColumn = 0; + } + $lineNumber += $prefixLines; + + while (++$lastGeneratedLine < $lineNumber) { + $groupedMapEncoded[] = ';'; + } + + $lineMapEncoded = []; + $lastGeneratedColumn = 0; + + foreach ($lineMap as $m) { + $generatedColumn = $m['generated_column'] + $prefixColumn; + + $mapEncoded = $this->encoder->encode($generatedColumn - $lastGeneratedColumn); + $lastGeneratedColumn = $generatedColumn; + + // find the index + if ($m['source_file']) { + $index = $this->findFileIndex($m['source_file']); + + if ($index !== false) { + $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); + $lastOriginalIndex = $index; + // lines are stored 0-based in SourceMap spec version 3 + $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); + $lastOriginalLine = $m['original_line'] - 1; + $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); + $lastOriginalColumn = $m['original_column']; + } + } + + $lineMapEncoded[] = $mapEncoded; + } + + $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; + } + + return rtrim(implode($groupedMapEncoded), ';'); + } + + /** + * Finds the index for the filename + * + * @param string $filename + * + * @return integer|false + */ + protected function findFileIndex($filename) + { + return $this->sourceKeys[$filename]; + } + + /** + * Normalize filename + * + * @param string $filename + * + * @return string + */ + protected function normalizeFilename($filename) + { + $filename = $this->fixWindowsPath($filename); + $rootpath = $this->options['sourceMapRootpath']; + $basePath = $this->options['sourceMapBasepath']; + + // "Trim" the 'sourceMapBasepath' from the output filename. + if (\strlen($basePath) && strpos($filename, $basePath) === 0) { + $filename = substr($filename, \strlen($basePath)); + } + + // Remove extra leading path separators. + if (strpos($filename, '\\') === 0 || strpos($filename, '/') === 0) { + $filename = substr($filename, 1); + } + + return $rootpath . $filename; + } + + /** + * Fix windows paths + * + * @param string $path + * @param boolean $addEndSlash + * + * @return string + */ + public function fixWindowsPath($path, $addEndSlash = false) + { + $slash = ($addEndSlash) ? '/' : ''; + + if (! empty($path)) { + $path = str_replace('\\', '/', $path); + $path = rtrim($path, '/') . $slash; + } + + return $path; + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Type.php b/plugins/admin/vendor/scssphp/scssphp/src/Type.php new file mode 100644 index 0000000..fb2a1d7 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Type.php @@ -0,0 +1,76 @@ + + */ +class Type +{ + const T_ASSIGN = 'assign'; + const T_AT_ROOT = 'at-root'; + const T_BLOCK = 'block'; + /** @deprecated */ + const T_BREAK = 'break'; + const T_CHARSET = 'charset'; + const T_COLOR = 'color'; + const T_COMMENT = 'comment'; + /** @deprecated */ + const T_CONTINUE = 'continue'; + /** @deprecated */ + const T_CONTROL = 'control'; + const T_CUSTOM_PROPERTY = 'custom'; + const T_DEBUG = 'debug'; + const T_DIRECTIVE = 'directive'; + const T_EACH = 'each'; + const T_ELSE = 'else'; + const T_ELSEIF = 'elseif'; + const T_ERROR = 'error'; + const T_EXPRESSION = 'exp'; + const T_EXTEND = 'extend'; + const T_FOR = 'for'; + const T_FUNCTION = 'function'; + const T_FUNCTION_REFERENCE = 'function-reference'; + const T_FUNCTION_CALL = 'fncall'; + const T_HSL = 'hsl'; + const T_HWB = 'hwb'; + const T_IF = 'if'; + const T_IMPORT = 'import'; + const T_INCLUDE = 'include'; + const T_INTERPOLATE = 'interpolate'; + const T_INTERPOLATED = 'interpolated'; + const T_KEYWORD = 'keyword'; + const T_LIST = 'list'; + const T_MAP = 'map'; + const T_MEDIA = 'media'; + const T_MEDIA_EXPRESSION = 'mediaExp'; + const T_MEDIA_TYPE = 'mediaType'; + const T_MEDIA_VALUE = 'mediaValue'; + const T_MIXIN = 'mixin'; + const T_MIXIN_CONTENT = 'mixin_content'; + const T_NESTED_PROPERTY = 'nestedprop'; + const T_NOT = 'not'; + const T_NULL = 'null'; + const T_NUMBER = 'number'; + const T_RETURN = 'return'; + const T_ROOT = 'root'; + const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once'; + const T_SELF = 'self'; + const T_STRING = 'string'; + const T_UNARY = 'unary'; + const T_VARIABLE = 'var'; + const T_WARN = 'warn'; + const T_WHILE = 'while'; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Util.php b/plugins/admin/vendor/scssphp/scssphp/src/Util.php new file mode 100644 index 0000000..62cd2a2 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Util.php @@ -0,0 +1,184 @@ + + * + * @internal + */ +class Util +{ + /** + * Asserts that `value` falls within `range` (inclusive), leaving + * room for slight floating-point errors. + * + * @param string $name The name of the value. Used in the error message. + * @param Range $range Range of values. + * @param array|Number $value The value to check. + * @param string $unit The unit of the value. Used in error reporting. + * + * @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin. + * + * @throws \ScssPhp\ScssPhp\Exception\RangeException + */ + public static function checkRange($name, Range $range, $value, $unit = '') + { + $val = $value[1]; + $grace = new Range(-0.00001, 0.00001); + + if (! \is_numeric($val)) { + throw new RangeException("$name {$val} is not a number."); + } + + if ($range->includes($val)) { + return $val; + } + + if ($grace->includes($val - $range->first)) { + return $range->first; + } + + if ($grace->includes($val - $range->last)) { + return $range->last; + } + + throw new RangeException("$name {$val} must be between {$range->first} and {$range->last}$unit"); + } + + /** + * Encode URI component + * + * @param string $string + * + * @return string + */ + public static function encodeURIComponent($string) + { + $revert = ['%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')']; + + return strtr(rawurlencode($string), $revert); + } + + /** + * mb_chr() wrapper + * + * @param integer $code + * + * @return string + */ + public static function mbChr($code) + { + // Use the native implementation if available, but not on PHP 7.2 as mb_chr(0) is buggy there + if (\PHP_VERSION_ID > 70300 && \function_exists('mb_chr')) { + return mb_chr($code, 'UTF-8'); + } + + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6) . \chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3F) + . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F); + } + + return $s; + } + + /** + * mb_strlen() wrapper + * + * @param string $string + * @return int + */ + public static function mbStrlen($string) + { + // Use the native implementation if available. + if (\function_exists('mb_strlen')) { + return mb_strlen($string, 'UTF-8'); + } + + if (\function_exists('iconv_strlen')) { + return (int) @iconv_strlen($string, 'UTF-8'); + } + + throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.'); + } + + /** + * mb_substr() wrapper + * @param string $string + * @param int $start + * @param null|int $length + * @return string + */ + public static function mbSubstr($string, $start, $length = null) + { + // Use the native implementation if available. + if (\function_exists('mb_substr')) { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + if (\function_exists('iconv_substr')) { + if ($start < 0) { + $start = static::mbStrlen($string) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = static::mbStrlen($string) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string)iconv_substr($string, $start, $length, 'UTF-8'); + } + + throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.'); + } + + /** + * mb_strpos wrapper + * @param string $haystack + * @param string $needle + * @param int $offset + * + * @return int|false + */ + public static function mbStrpos($haystack, $needle, $offset = 0) + { + if (\function_exists('mb_strpos')) { + return mb_strpos($haystack, $needle, $offset, 'UTF-8'); + } + + if (\function_exists('iconv_strpos')) { + return iconv_strpos($haystack, $needle, $offset, 'UTF-8'); + } + + throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.'); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Util/Path.php b/plugins/admin/vendor/scssphp/scssphp/src/Util/Path.php new file mode 100644 index 0000000..f399e41 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Util/Path.php @@ -0,0 +1,77 @@ +parseValue($source, $value)) { + throw new \InvalidArgumentException(sprintf('Invalid value source "%s".', $source)); + } + + return $value; + } + + /** + * Converts a PHP value to a Sass value + * + * The returned value is guaranteed to be supported by the + * Compiler methods for registering custom variables. No other + * guarantee about it is provided. It should be considered + * opaque values by the caller. + * + * @param mixed $value + * + * @return mixed + */ + public static function fromPhp($value) + { + if ($value instanceof Number) { + return $value; + } + + if (is_array($value) && isset($value[0]) && \in_array($value[0], [Type::T_NULL, Type::T_COLOR, Type::T_KEYWORD, Type::T_LIST, Type::T_MAP, Type::T_STRING])) { + return $value; + } + + if ($value === null) { + return Compiler::$null; + } + + if ($value === true) { + return Compiler::$true; + } + + if ($value === false) { + return Compiler::$false; + } + + if ($value === '') { + return Compiler::$emptyString; + } + + if (\is_int($value) || \is_float($value)) { + return new Number($value, ''); + } + + if (\is_string($value)) { + return [Type::T_STRING, '"', [$value]]; + } + + throw new \InvalidArgumentException(sprintf('Cannot convert the value of type "%s" to a Sass value.', gettype($value))); + } +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Version.php b/plugins/admin/vendor/scssphp/scssphp/src/Version.php new file mode 100644 index 0000000..62c8006 --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Version.php @@ -0,0 +1,23 @@ + + */ +class Version +{ + const VERSION = '1.8.1'; +} diff --git a/plugins/admin/vendor/scssphp/scssphp/src/Warn.php b/plugins/admin/vendor/scssphp/scssphp/src/Warn.php new file mode 100644 index 0000000..592b44c --- /dev/null +++ b/plugins/admin/vendor/scssphp/scssphp/src/Warn.php @@ -0,0 +1,84 @@ + + + + + + \ No newline at end of file diff --git a/plugins/anchors/CHANGELOG.md b/plugins/anchors/CHANGELOG.md new file mode 100644 index 0000000..d5ada54 --- /dev/null +++ b/plugins/anchors/CHANGELOG.md @@ -0,0 +1,65 @@ +# v1.6.0 +## 11/24/2021 + +1. [](#new) + * Added new option `copy_to_clipboard` to automatically copy the link to clipboard +2. [](#improved) + * Removed reliance on `JQuery` for DOM ready check + * Updated `anchors.js` to version 4.3.1 + +# v1.5.4 +## 12/02/2020 + +1. [](#improved) + * Updated `anchors.js` to version 4.3.0 + * Requires Grav 1.6 + +# v1.5.3 +## 03/30/2019 + +1. [](#improved) + * Updated `anchors.js` to version 4.2.0 + * Improved documentation + +# v1.5.2 +## 09/28/2017 + +1. [](#improved) + * Updated `anchors.js` to version 4.1.0 + +# v1.5.1 +## 07/14/2016 + +1. [](#improved) + * Translate some blueprint options + +# v1.5.0 +## 01/06/2016 + +1. [](#improved) + * Disable anchors in Admin + +# v1.4.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.3.0 +## 07/20/2015 + +1. [](#new) + * Updated `anchors.js` to version 1.2.1 + * Added new options such as 'placement', 'visible', 'icon' and 'class' + +# v1.2.0 +## 03/01/2015 + +1. [](#new) + * Updated `anchors.js` to version 0.3.0 + +# v1.1.0 +## 11/30/2014 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/anchors/LICENSE b/plugins/anchors/LICENSE new file mode 100644 index 0000000..484793a --- /dev/null +++ b/plugins/anchors/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +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/plugins/anchors/README.md b/plugins/anchors/README.md new file mode 100644 index 0000000..cb615da --- /dev/null +++ b/plugins/anchors/README.md @@ -0,0 +1,106 @@ +# Grav Anchors Plugin + + +`anchors` is a [Grav](http://github.com/getgrav/grav) plugin that provides automatic header anchors via the [anchorjs](http://bryanbraun.github.io/anchorjs) Vanilla JS plugin. + +# Installation + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). From the root of your Grav install type: + + bin/gpm install anchors + +## Manual Installation + +If for some reason you can't use GPM you can manually install this plugin. Download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `anchors`. + +You should now have all the plugin files under + + /your/site/grav/user/plugins/anchors + +# Usage + +To best understand how Anchors works, you should read through the original [project documentation](https://github.com/bryanbraun/anchorjs). + +## Show Menu of Anchors + +If you want to use the generated links to also generate a menu from these anchors, just put the function below in the template file Twig: + +``` +{{ anchors(content, tag, terms) }} +``` + +The function accepts 3 parameters: + +* **content:** this parameter is the content of the page in which the function will search for all content and separate only the tags and their contents defined by the second parameter. E.g. `page.content` +* **tag:** this parameter will be the string of the tag used to make the menu. E.g. `h2` +* **terms:** this parameter is to exclude terms that you do not wish to include in the menu formation that is between the tag passed in the second parameter. The value passed is a string separated by comma to identify each term. Ex: `title 01, title 02` + +For example: + +``` +{{ anchors(page.content, 'h2') }} +``` + +When rendered the function will return a formed HTML with `
    ` and the links of the anchors in each `
  • `. + +## Configuration: + +Simply copy the `user/plugins/breadcrumbs/anchors.yaml` into `user/config/plugins/anchors.yaml` and make your modifications. + + enabled: true # enable or disable the plugin + active: true # active by default, if false then you must activate per-page + selectors: 'h1,h2,h3,h4' # css elements to activate on. Uses jQuery style selectors + placement: right # either "left" or "right" + visible: hover # Active on "hover" or "always" visible + icon: # default link or a specific character like: #, ¶, ❡, and §. + class: # adds the provided class to the anchor html + truncate: 64 # truncates the generated ID to the specified character length + +You can override any default settings from the page headers: + +eg: + + --- + title: Sample Code With Custom Theme + anchors: + active: true + selectors: .blog h1, .blog h2 + --- + + # Header + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas accumsan porta diam, + nec sagittis odio euismod nec. Etiam eu rutrum eros. + + ## Sub Header + + Proin commodo lobortis elementum. + Integer vel ultrices massa, nec ornare urna. Phasellus tincidunt rutrum dolor, vestibulum + faucibus ligula laoreet id. Donec hendrerit arcu vitae lacus mattis facilisis. Praesent + tortor nibh, pulvinar nec orci ac, rhoncus pharetra nunc. + + +You can also disable anchors for a particular page if causes issues: + + --- + title: Sample Code with Highlight disabled + anchors: + active: false + --- + + # Header + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas accumsan porta diam, + nec sagittis odio euismod nec. Etiam eu rutrum eros. + + ## Sub Header + + Proin commodo lobortis elementum. + Integer vel ultrices massa, nec ornare urna. Phasellus tincidunt rutrum dolor, vestibulum + faucibus ligula laoreet id. Donec hendrerit arcu vitae lacus mattis facilisis. Praesent + tortor nibh, pulvinar nec orci ac, rhoncus pharetra nunc. + + +> Note: If you want to see this plugin in action, have a look at [Grav Learn Site](http://learn.getgrav.org) diff --git a/plugins/anchors/anchors.php b/plugins/anchors/anchors.php new file mode 100644 index 0000000..45d4797 --- /dev/null +++ b/plugins/anchors/anchors.php @@ -0,0 +1,109 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize configuration + */ + public function onPluginsInitialized() + { + if ($this->isAdmin()) { + $this->active = false; + } else { + $this->enable([ + 'onPageInitialized' => ['onPageInitialized', 0], + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0], + 'onTwigExtensions' => ['onTwigExtensions', 0] + ]); + } + } + + public function onTwigExtensions() + { + $config = $this->config->get('plugins.anchors.selectors'); + require_once(__DIR__ . '/twig/AnchorsTwigExtension.php'); + $this->grav['twig']->twig->addExtension(new AnchorsTwigExtension($config)); + } + + /** + * Initialize configuration + */ + public function onPageInitialized() + { + $defaults = (array) $this->config->get('plugins.anchors'); + + /** @var Page $page */ + $page = $this->grav['page']; + if (isset($page->header()->anchors)) { + $this->config->set('plugins.anchors', array_merge($defaults, $page->header()->anchors)); + } + } + + /** + * if enabled on this page, load the JS + CSS and set the selectors. + */ + public function onTwigSiteVariables() + { + if ($this->config->get('plugins.anchors.active')) { + $selectors = $this->config->get('plugins.anchors.selectors', 'h1,h2,h3,h4'); + $visible = "visible: '{$this->config->get('plugins.anchors.visible', 'hover')}',"; + $placement = "placement: '{$this->config->get('plugins.anchors.placement', 'right')}',"; + $icon = $this->config->get('plugins.anchors.icon') ? "icon: '{$this->config->get('plugins.anchors.icon')}'," : ''; + $class = $this->config->get('plugins.anchors.class') ? "class: '{$this->config->get('plugins.anchors.class')}'," : ''; + $truncate = "truncate: {$this->config->get('plugins.anchors.truncate', 64)}"; + $this->grav['assets']->addJs('plugin://anchors/js/anchor.min.js'); + + $anchors_init = + "document.addEventListener('DOMContentLoaded', function() { + anchors.options = { + $visible + $placement + $icon + $class + $truncate + }; + anchors.add('$selectors'); + });"; + + + $this->grav['assets']->addInlineJs($anchors_init); + + if ($this->config->get('plugins.anchors.copy_to_clipboard')) { + $clipboard_init = + 'document.addEventListener("DOMContentLoaded", function() { + for (var r=0; r < document.getElementsByClassName("anchorjs-link").length; r++) { + var danchors = document.getElementsByClassName("anchorjs-link"); + var danchorshref = danchors[r].href; + danchors[r].setAttribute("data-clipboard-text",danchorshref); + } + let anchorjsLinks = document.querySelectorAll(".anchorjs-link"); + anchorjsLinks.forEach(el => { + el.addEventListener("click", event => { + event.preventDefault(); + // add custom "copy to clipboard" code + new ClipboardJS(".anchorjs-link"); + }); + }); + });'; + + $this->grav['assets']->addJs('plugin://anchors/js/clipboard.min.js'); + $this->grav['assets']->addInlineJs($clipboard_init); + } + } + } +} diff --git a/plugins/anchors/anchors.yaml b/plugins/anchors/anchors.yaml new file mode 100644 index 0000000..33e385f --- /dev/null +++ b/plugins/anchors/anchors.yaml @@ -0,0 +1,9 @@ +enabled: true # enable or disable the plugin +active: true # active by default, if false then you must activate per-page +selectors: 'h1,h2,h3,h4' # css elements to activate on. Uses jQuery style selectors +placement: right # either "left" or "right" +visible: hover # Active on "hover" or "always" visible +icon: # default link or a specific character like: #, ¶, ❡, and §. +class: # adds the provided class to the anchor html +truncate: 64 # truncates the generated ID to the specified character length +copy_to_clipboard: false # copies the link to clipboard when clicking diff --git a/plugins/anchors/blueprints.yaml b/plugins/anchors/blueprints.yaml new file mode 100644 index 0000000..05276f7 --- /dev/null +++ b/plugins/anchors/blueprints.yaml @@ -0,0 +1,107 @@ +name: Anchors +type: plugin +slug: anchors +version: 1.6.0 +description: "This plugin provides automatic header anchors via the [anchorjs](http://bryanbraun.github.io/anchorjs) jQuery plugin." +icon: anchor +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-anchors +demo: http://learn.getgrav.org +keywords: anchor, header, plugin, code +bugs: https://github.com/getgrav/grav-plugin-anchors/issues +license: MIT +dependencies: + - { name: grav, version: '>=1.6.0' } + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + active: + type: toggle + label: Active + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + help: Activate for all pages. If disabled then you must activate per-page + + selectors: + type: text + label: Selectors + size: large + default: 'h1,h2,h3,h4' + placeholder: "Anchor Selectors" + help: Comma separated list of header selectors to activate on + + placement: + type: select + label: Placement + classes: fancy + help: "Either `left` or `right`" + default: 'right' + options: + 'left': 'left' + 'right': 'right' + + visible: + type: select + label: Visible + classes: fancy + help: "Hover activates on `hover` else will always display" + default: 'hover' + options: + 'hover': 'hover' + 'always': 'always' + + icon: + type: text + label: Icon + size: medium + default: '' + help: "Replace the default link icon with the character(s) provided, e.g. #, ¶, ❡ or §" + + class: + type: text + label: Class + size: medium + default: '' + help: "Adds the provided class to the anchor html" + + truncate: + type: text + size: x-small + label: Truncate + help: "Truncates the generated ID to the specified character length" + default: 64 + validate: + type: number + min: 0 + + copy_to_clipboard: + type: toggle + label: Copy to clipboard + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + help: Instead of scrolling to the item when clicking, you can enabled this option to copy the link to the clipboard automatically \ No newline at end of file diff --git a/plugins/anchors/hebe.json b/plugins/anchors/hebe.json new file mode 100644 index 0000000..28916e3 --- /dev/null +++ b/plugins/anchors/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-anchors", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/anchors" + } + ] + } + } + } +} diff --git a/plugins/anchors/js/anchor.min.js b/plugins/anchors/js/anchor.min.js new file mode 100644 index 0000000..1c2b86f --- /dev/null +++ b/plugins/anchors/js/anchor.min.js @@ -0,0 +1,9 @@ +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat +// +// AnchorJS - v4.3.1 - 2021-04-17 +// https://www.bryanbraun.com/anchorjs/ +// Copyright (c) 2021 Bryan Braun; Licensed MIT +// +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat +!function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(this,function(){"use strict";return function(A){function d(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function w(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],d(this.options),this.isTouchDevice=function(){return Boolean("ontouchstart"in window||window.TouchEvent||window.DocumentTouch&&document instanceof DocumentTouch)},this.add=function(A){var e,t,o,i,n,s,a,c,r,l,h,u,p=[];if(d(this.options),"touch"===(l=this.options.visible)&&(l=this.isTouchDevice()?"always":"hover"),0===(e=w(A=A||"h2, h3, h4, h5, h6")).length)return this;for(null===document.head.querySelector("style.anchorjs")&&((u=document.createElement("style")).className="anchorjs",u.appendChild(document.createTextNode("")),void 0===(A=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(u):document.head.insertBefore(u,A),u.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",u.sheet.cssRules.length),u.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/plugins/anchors/js/clipboard.min.js b/plugins/anchors/js/clipboard.min.js new file mode 100644 index 0000000..54b3c46 --- /dev/null +++ b/plugins/anchors/js/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1config = $config; + } + + public function getName() + { + return 'AnchorsTwigExtension'; + } + public function getFunctions() + { + return [ + new \Twig_SimpleFunction('anchors', [$this, 'anchorsFunction']) + ]; + } + + + /** + * Get a list of anchors link + * + * @param $content + * @param $tag + * @return string + */ + public function anchorsFunction($content, $tag = 'h2', $terms = null) + { + + $configTags = array_map('trim', explode(',',$this->config)); + + if (in_array($tag, $configTags)){ + $textMenu = []; + $rx = '/<'.$tag.'>(.*)<\/'.$tag.'>/'; + + preg_match_all($rx, $content, $group); + + if (!empty($group[1])){ + + if (!empty($terms)){ + $termsArray = array_map('trim', explode(',',$terms)); + $elements = array_diff($group[1],$termsArray); + }else{ + $elements = $group[1]; + } + + foreach($elements as $element){ + $textMenu[] = $element; + } + $html = $this->getHtmlTag($textMenu); + }else{ + $html = ''; + } + }else{ + $tag = current($configTags); + $html = $this->anchorsFunction($tag, $content); + } + + return $html; + + } + + + /** + * Mount the html of anchors link + * + * @param $itens + * @return string + */ + private function getHtmlTag($itens) + { + $html = ''; + + $html .= ''; + + return $html; + } + + + /** + * Get url whithout special characters + * + * @param $text + * @return string + */ + private function getUrl($text) + { + $rx1 = array('ä','ã','à','á','â','ê','ë','è','é','ï','ì','í','ö','õ','ò','ó','ô','ü','ù','ú','û','À','Á','Â','Ã','É','Ê','Ô','Í','Ó','Õ','Ú','ñ','Ñ','ç','Ç'); + $rx2 = '/\'/'; + $rx3 = '/[& \+\$,:;=\?@"#\{\}\|\^~\[`%!\'<>\]\.\/\(\)\*ºª]/'; + $rx4 = '/-{2,}/'; + $rx5 = '/\^-+|-+$/'; + + + $urlAnchor1 = str_replace('–', '-', str_replace($rx1, '', $text)); + $urlAnchor2 = preg_replace($rx2, '', $urlAnchor1); + $urlAnchor3 = preg_replace($rx3, '-', $urlAnchor2); + $urlAnchor4 = $this->textLimit(preg_replace($rx4, '-', $urlAnchor3), 64); + $urlAnchor5 = preg_replace($rx5, '', $urlAnchor4); + + $urlAnchorFinal = strtolower($urlAnchor5); + + return $urlAnchorFinal; + } + + + /** + * It limit characters total in exit + * + * @param $text + * @param $limit + * @param bool|true $url //verify if type content is url or title + * @return string + */ + private function textLimit($text, $limit, $url = true) + { + $count = strlen($text); + if ( $count >= $limit ) { + if ($url){ + $text = substr($text, 0, $limit); + return $text; + }else { + $text = substr($text, 0, strrpos(substr($text, 0, $limit), ' ')) . '...'; + return $text; + } + } + else{ + return $text; + } + } +} diff --git a/plugins/archives/CHANGELOG.md b/plugins/archives/CHANGELOG.md new file mode 100644 index 0000000..af12a57 --- /dev/null +++ b/plugins/archives/CHANGELOG.md @@ -0,0 +1,90 @@ +# v2.0.2 +## 12/02/2020 + +1. [](#improved) + * Add taxonomy values date format customization [#29](https://github.com/getgrav/grav-plugin-archives/pull/29) + +# v2.0.1 +## 07/09/2020 + +1. [](#bugfix) + * Fix for archives on homepage [#26](https://github.com/getgrav/grav-plugin-archives/issues/26) + +# v2.0.0 +## 04/06/2020 + +1. [](#new) + * New per-page configuration to allow for multiple 'archives' in a single site + * Added new `page@` filter support to allow configuration from page collection [#20](https://github.com/getgrav/grav-plugin-archives/pull/20) +1. [](#improved) + * Added more sort-by options + +# v1.6.1 +## 02/24/2020 + +1. [](#new) + * Pass phpstan level 1 tests + * Require Grav v.1.6 +1. [](#bugfix) + * Exclude empty folders from archive + * Fixed issue in 1.7 due to `validation: strict` and missing `taxonomy_names` blueprint item + +# v1.6.0 +## 04/17/2019 + +1. [](#improved) + * Only included published pages in collection [#24](https://github.com/getgrav/grav-plugin-archives/issues/24) + * Translate the date to the format specified [#9](https://github.com/getgrav/grav-plugin-archives/pull/9) + +# v1.5.1 +## 05/16/2017 + +1. [](#improved) + * Added another date option to blueprints [#7](https://github.com/getgrav/grav-plugin-archives/pull/7) + +# v1.5.0 +## 07/14/2016 + +1. [](#improved) + * Allow to configure the taxonomy names that form the URL, instead of hardcoding `archives_month` and `archives_year` + * Allow to use @self in the filters, useful when adding the archives into a blog posts listing page + +# v1.4.1 +## 05/03/2016 + +1. [](#bugfix) + * Fixed translated months + +# v1.4.0 +## 01/06/2016 + +1. [](#improved) + * Allow for translated months +1. [](#bugfix) + * Fix blueprints by adding the category to filters + +# v1.3.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.2.1 +## 02/19/2015 + +2. [](#improved) + * Implemented new `param_sep` variable from Grav 0.9.18 + +# v1.2.0 +## 01/08/2015 + +1. [](#new) + * Added new `archives_year` automatic taxonomy type +2. [](#improved) + * Automatically adds taxonomy types (`archives_month`, `archives_year`) rather than requiring you to manually edit `site.yaml` + +# v1.10 +## 11/30/2014 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/archives/LICENSE b/plugins/archives/LICENSE new file mode 100644 index 0000000..484793a --- /dev/null +++ b/plugins/archives/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +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/plugins/archives/README.md b/plugins/archives/README.md new file mode 100644 index 0000000..5c06139 --- /dev/null +++ b/plugins/archives/README.md @@ -0,0 +1,142 @@ +# Grav Archives Plugin + +`Archives` is a [Grav](http://github.com/getgrav/grav) plugin that automatically appends a `month_year` taxonomy to all pages. It then provides a `partials\archives.html.twig` template which you can include in a blog sidebar, that then is able to create links that will display pages from that month/year. This is a very handy feature to have for blogs. + +# Installation + +Installing the Archives plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +![GPM Installation](assets/readme_1.png) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install archives + +This will install the Archives plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/archives`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `archives`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-archives) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/archives + +>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate. + +# Usage + +The `archives` plugin comes with some sensible default configuration, that are pretty self-explanatory: + +# Config Defaults + +``` +enabled: true +built_in_css: true +date_display_format: 'F Y' +show_count: true +limit: 12 +taxonomy_names: + month: archives_month + year: archives_year +#Defaults +order: + by: date + dir: desc +filters: + category: blog +filter_combinator: and +#New Page-Specific Configurations +page_specific_config: + - route: '/blog' + order: + by: date + dir: desc + filters: + page@: '/blog' + filter_combinator: and +``` + +If you need to change any value, then the best process is to copy the [archives.yaml](archives.yaml) file into your `users/config/plugins/` folder (create it if it doesn't exist), and then modify there. This will override the default settings. + +## Filter Types + +#### category + +The legacy approach is to provide a specific category taxonomy filter, or multiple categories: + +``` +filters: + category 'blog-post' +``` + +#### taxonomy@ + +You can use sophisticated taxonomy filtering with the same mechanism as page taxonomy filtering: + +``` +filters: + taxonomy@.tag: photography # taxonomy called tag is set to photography +``` + +or: + +``` +filters: + taxonomy@: {tag: birds, category: blog} # taxonomy with tag=birds && category=blog +``` + +#### page@ + +You can reference a specific page's collection via the page@ filter: + +``` +filters: + page@: '/blog' # Use the collection defined in the header of `/blog` page +``` + +#### self@ + +You can also list the current children, without having to search for a taxonomy term by using + +``` +filters: + - self@ # use the children defined in the current page +``` + +# Template Override + +Something you might want to do is to override the look and feel of the archives, and with Grav it is super easy. + +Copy the template file [templates/partials/archives.html.twig](templates/partials/archives.html.twig) into the `templates/partials` folder of your custom theme, and that is it. + +``` +/your/site/grav/user/themes/custom-theme/templates/partials/archives.html.twig +``` + +You can now edit the override and tweak it however you prefer. + +# Updating + +As development for Archives continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Archives is easy, and can be done through Grav's GPM system, as well as manually. + +## GPM Update (Preferred) + +The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: + + bin/gpm update archives + +This command will check your Grav install to see if your Archives plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. + +## Manual Update + +Manually updating Archives is pretty simple. Here is what you will need to do to get this done: + +* Delete the `your/site/user/plugins/archives` directory. +* Downalod the new version of the Archives plugin from either [GitHub](https://github.com/getgrav/grav-plugin-archives) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras). +* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `archives`. +* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`. + +> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. diff --git a/plugins/archives/archives.php b/plugins/archives/archives.php new file mode 100644 index 0000000..5cbde93 --- /dev/null +++ b/plugins/archives/archives.php @@ -0,0 +1,267 @@ + [ + ['autoload', 100001], + ['onPluginsInitialized', 0] + ] + ]; + } + + /** + * [onPluginsInitialized:100000] Composer autoload. + * + * @return ClassLoader + */ + public function autoload() + { + return require __DIR__ . '/vendor/autoload.php'; + } + + /** + * Initialize configuration + */ + public function onPluginsInitialized() + { + if ($this->isAdmin()) { + $this->active = false; + return; + } + + $this->month_taxonomy_value = $this->config->get('plugins.archives.taxonomy_values.month'); + $this->year_taxonomy_value = $this->config->get('plugins.archives.taxonomy_values.year'); + + $this->month_taxonomy_name = $this->config->get('plugins.archives.taxonomy_names.month'); + $this->year_taxonomy_name = $this->config->get('plugins.archives.taxonomy_names.year'); + + // Dynamically add the needed taxonomy types to the taxonomies config + $taxonomy_config = array_merge((array)$this->config->get('site.taxonomies'), [$this->month_taxonomy_name, $this->year_taxonomy_name]); + $this->config->set('site.taxonomies', $taxonomy_config); + + $this->enable([ + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + 'onPageProcessed' => ['onPageProcessed', 0], + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] + ]); + } + + /** + * Add current directory to twig lookup paths. + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Add + * + * @param Event $event + */ + public function onPageProcessed(Event $event) + { + /** @var PageInterface $page */ + $page = $event['page']; + if (!$page->isPage()) { + return; + } + + $taxonomy = $page->taxonomy(); + + // track month taxonomy using month_taxonomy_value format: + if (!isset($taxonomy[$this->month_taxonomy_name])) { + $taxonomy[$this->month_taxonomy_name] = array(strtolower(date($this->month_taxonomy_value, $page->date()))); + } + + // track year taxonomy using year_taxonomy_value format: + if (!isset($taxonomy[$this->year_taxonomy_name])) { + $taxonomy[$this->year_taxonomy_name] = array(date($this->year_taxonomy_value, $page->date())); + } + + // set the modified taxonomy back on the page object + $page->taxonomy($taxonomy); + } + + /** + * Set needed variables to display archives. + */ + public function onTwigSiteVariables() + { + /** @var PageInterface $page */ + $page = $this->grav['page']; + + // If a page exists merge the archive config if set + if ($page) { + $this->config->set('plugins.archives', $this->mergeConfig($page)); + } + + // See if there is page-specific configuration set (new in Archives 2.0) + $page_specific_config = $this->config->get('plugins.archives.page_specific_config'); + $archives = $archives_url = null; + + if ($page && is_array($page_specific_config)) { + foreach ($page_specific_config as $page_config) { + // Does the page config match route of this current page + if (isset($page_config['route']) && $this->isValidPageRoute($page, $page_config['route'])) { + $filters = $page_config['filters'] ?? (array) $this->config->get('plugins.archives.filters'); + + // get around limitation of no YAML filtering support in list field + if (is_string($filters)) { + $filters = Yaml::parse($filters); + } + + $operator = $page_config['filter_combinator'] ?? $this->config->get('plugins.archives.filter_combinator'); + $order = [ + 'by' => $page_config['order_by'] ?? $this->config->get('plugins.archives.order.by'), + 'dir' => $page_config['order_dir'] ?? $this->config->get('plugins.archives.order.dir') + ]; + $archives = $this->getArchives((array)$filters, $operator, $order); + $archives_url = $this->grav['base_url_absolute'] . $page_config['route']; + break; + } + } + } else { + // get the plugin filters setting + $filters = (array) $this->config->get('plugins.archives.filters'); + $operator = $this->config->get('plugins.archives.filter_combinator'); + $order = $this->config->get('plugins.archives.order'); + $archives = $this->getArchives((array)$filters, $operator, $order); + } + + // add the archives_start date to the twig variables + $this->grav['twig']->twig_vars['archives_show_count'] = $this->config->get('plugins.archives.show_count'); + $this->grav['twig']->twig_vars['archives_data'] = $archives; + $this->grav['twig']->twig_vars['archives_url'] = $archives_url; + } + + /** Something like this should be in Page object in future */ + protected function isValidPageRoute(PageInterface $page, $route) + { + $uri = $this->grav['uri']; + $page_routes = [$page->route(), $page->rawRoute()]; + $page_routes[] = str_replace($page->canonical(false), $uri->rootUrl(true), ''); + $page_routes = array_merge($page_routes, $page->routeAliases()); + + foreach ($page_routes as $proute) { + if (Utils::startsWith($proute, $route, false)) { + return true; + } + } + return false; + } + + protected function getArchives($filters, $operator, $order) + { + $order_by = $order['by'] ?? 'date'; + $order_dir = $order['dir'] ?? 'desc'; + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + + /** @var PageInterface $page */ + $page = $this->grav['page']; + + /** @var Taxonomy $taxonomy_map */ + $taxonomy_map = $this->grav['taxonomy']; + $taxonomies = []; + $find_taxonomy = []; + $archives = []; + $start_date = time(); + + + $new_approach = false; + $collection = null; + $page_filter = null; + + if (!$filters || (count($filters) === 1 && !reset($filters))){ + $collection = $pages->all(); + } else { + foreach ($filters as $key => $filter) { + // flatten item if it's wrapped in an array + if (is_int($key)) { + if (is_array($filter)) { + $key = key($filter); + $filter = $filter[$key]; + } else { + $key = $filter; + } + } + // see if the filter uses the new 'items-type' syntax + if ($key === '@self' || $key === 'self@') { + $new_approach = true; + } elseif ($key === '@page' || $key === 'page@') { + $page_filter = $filter; + } elseif ($key === '@taxonomy' || $key === 'taxonomy@') { + $taxonomies = $filter === false ? false : array_merge($taxonomies, (array) $filter); + } else { + $find_taxonomy[$key] = $filter; + } + } + if ($new_approach) { + $collection = $page->children(); + } elseif ($page_filter) { + $collection = $pages->find($page_filter)->children(); + } else { + $collection = new Collection(); + $collection->append($taxonomy_map->findTaxonomy($find_taxonomy, $operator)->toArray()); + } + } + + // reorder the collection based on settings + $collection = $collection->order($order_by, $order_dir)->published(); + $date_format = $this->config->get('plugins.archives.date_display_format'); + // drop unpublished and un-routable pages + $collection->published()->routable(); + + // loop over new collection of pages that match filters + foreach ($collection as $page) { + // update the start date if the page date is older + $start_date = $page->date() < $start_date ? $page->date() : $start_date; + $archives[date($date_format, $page->date())][] = $page; + } + + // slice the array to the limit you want + $archives = array_slice($archives, 0, (int)$this->config->get('plugins.archives.limit'), is_string(reset($archives)) ? false : true ); + + return $archives; + } +} diff --git a/plugins/archives/archives.yaml b/plugins/archives/archives.yaml new file mode 100644 index 0000000..0713151 --- /dev/null +++ b/plugins/archives/archives.yaml @@ -0,0 +1,27 @@ +enabled: true +built_in_css: true +date_display_format: 'F Y' +show_count: true +limit: 12 +taxonomy_names: + month: archives_month + year: archives_year +taxonomy_values: + month: 'M_Y' + year: 'Y' +#Defaults +order: + by: date + dir: desc +filters: + category: blog +filter_combinator: and +#New Page-Specific Configurations +page_specific_config: + - route: '/blog' + order: + by: date + dir: desc + filters: + page@: '/blog' + filter_combinator: and diff --git a/plugins/archives/assets/readme_1.png b/plugins/archives/assets/readme_1.png new file mode 100644 index 0000000..1ab4cb7 Binary files /dev/null and b/plugins/archives/assets/readme_1.png differ diff --git a/plugins/archives/blueprints.yaml b/plugins/archives/blueprints.yaml new file mode 100644 index 0000000..a2ffebc --- /dev/null +++ b/plugins/archives/blueprints.yaml @@ -0,0 +1,190 @@ +name: Archives +version: 2.0.2 +type: plugin +slug: archives +description: The **Archives** plugin creates links for pages grouped by month/year +icon: university +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-archives +demo: http://demo.getgrav.org/blog-skeleton +keywords: archives, plugin, blog, month, year, date, navigation, history +bugs: https://github.com/getgrav/grav-plugin-archives/issues +license: MIT +dependencies: + - { name: grav, version: '>=1.6.0' } + +form: + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + built_in_css: + type: toggle + label: Use built in CSS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + date_display_format: + type: select + size: medium + classes: fancy + label: Date Format + default: 'jS M Y' + options: + 'F jS Y': "January 1st 2014" + 'l jS of F': "Monday 1st of January" + 'D, m M Y': "Mon, 01 Jan 2014" + 'd-m-y': "01-01-14" + 'jS M Y': "10th Feb 2014" + 'F Y': "Jan 2015" + + show_count: + type: toggle + label: Show Count + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + limit: + type: text + size: x-small + label: Count Limit + validate: + type: number + min: 1 + + taxonomy_names: + type: array + label: Taxonomy Names + placeholder_key: e.g. month + placeholder_value: e.g. archives_month + + taxonomy_values: + type: array + label: Taxonomy Values (date format) + placeholder_key: e.g. month + placeholder_value: e.g. 'M_Y' + + defaults_section: + type: section + underline: true + title: Default Configuration + + order.by: + type: select + size: medium + classes: fancy + label: Order Type + options: + default: Default - based on folder name + title: Title - based on title field in header + basename: Basename - based on the alphabetic folder name + date: Date - based on date field in header + modified: Modified - based on the modified timestamp + folder: Folder - based on prefix-less folder name + random: Random - order is randomized + + order.dir: + type: select + size: medium + label: Order Direction + default: desc + options: + asc: Ascending + desc: Descending + + filters: + type: textarea + yaml: true + label: Filter + placeholder: "page@: '/blog'" + validate: + type: yaml + + filter_combinator: + type: select + size: medium + classes: fancy + label: Filter Combinator + default: and + options: + and: 'And - Boolean &&' + or: 'Or - Boolean ||' + + pageconfig_section: + type: section + underline: true + title: Page-Specific Configuration + + page_specific_config: + type: list + label: Configurations + + fields: + .route: + type: text + label: Route + placeholder: '/blog' + validate: + required: true + .filters: + type: textarea + yaml: true + label: Filter + placeholder: "page@: '/blog'" + validate: + type: yaml + .filter_combinator: + type: select + size: medium + classes: fancy + label: Filter Combinator + default: '' + options: + '': Use Default + and: 'And - Boolean &&' + or: 'Or - Boolean ||' + .order_by: + type: select + size: medium + classes: fancy + label: Order Type + default: '' + options: + '': Use Default + default: Default - based on folder name + title: Title - based on title field in header + basename: Basename - based on the alphabetic folder name + date: Date - based on date field in header + modified: Modified - based on the modified timestamp + folder: Folder - based on prefix-less folder name + random: Random - order is randomized + .order_dir: + type: select + size: medium + label: Order Direction + default: '' + options: + '': Use Default + asc: Ascending + desc: Descending diff --git a/plugins/archives/composer.json b/plugins/archives/composer.json new file mode 100644 index 0000000..f8ca9f4 --- /dev/null +++ b/plugins/archives/composer.json @@ -0,0 +1,27 @@ +{ + "name": "grav-plugin-archives", + "type": "grav-plugin", + "description": "Archives plugin for Grav CMS", + "keywords": ["archives"], + "homepage": "https://github.com/getgrav/grav-plugin-archives", + "license": "MIT", + "authors": [ + { + "name": "Team Grav", + "email": "devs@getgrav.org", + "homepage": "http://getgrav.org", + "role": "Developer" + } + ], + "require": { + "php": ">=7.1.3" + }, + "autoload": { + "classmap": ["archives.php"] + }, + "config": { + "platform": { + "php": "7.1.3" + } + } +} diff --git a/plugins/archives/composer.lock b/plugins/archives/composer.lock new file mode 100644 index 0000000..3aa0f4c --- /dev/null +++ b/plugins/archives/composer.lock @@ -0,0 +1,22 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d34df828bb8259e86ecbc6cdc9a971d1", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.3" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.1.3" + } +} diff --git a/plugins/archives/templates/partials/archives.html.twig b/plugins/archives/templates/partials/archives.html.twig new file mode 100644 index 0000000..cc1590e --- /dev/null +++ b/plugins/archives/templates/partials/archives.html.twig @@ -0,0 +1,13 @@ + diff --git a/plugins/archives/vendor/autoload.php b/plugins/archives/vendor/autoload.php new file mode 100644 index 0000000..029d09f --- /dev/null +++ b/plugins/archives/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/plugins/archives/vendor/composer/LICENSE b/plugins/archives/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/plugins/archives/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/plugins/archives/vendor/composer/autoload_classmap.php b/plugins/archives/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..3a13ca7 --- /dev/null +++ b/plugins/archives/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $baseDir . '/archives.php', +); diff --git a/plugins/archives/vendor/composer/autoload_namespaces.php b/plugins/archives/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/plugins/archives/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit6a19f37247b1cd77a801d32450df2931::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/plugins/archives/vendor/composer/autoload_static.php b/plugins/archives/vendor/composer/autoload_static.php new file mode 100644 index 0000000..ab936b6 --- /dev/null +++ b/plugins/archives/vendor/composer/autoload_static.php @@ -0,0 +1,20 @@ + __DIR__ . '/../..' . '/archives.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInit6a19f37247b1cd77a801d32450df2931::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/archives/vendor/composer/installed.json b/plugins/archives/vendor/composer/installed.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/plugins/archives/vendor/composer/installed.json @@ -0,0 +1 @@ +[] diff --git a/plugins/aura-authors/.gitattributes b/plugins/aura-authors/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/plugins/aura-authors/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/plugins/aura-authors/CHANGELOG.md b/plugins/aura-authors/CHANGELOG.md new file mode 100644 index 0000000..74d004c --- /dev/null +++ b/plugins/aura-authors/CHANGELOG.md @@ -0,0 +1,5 @@ +# v1.0.0 +## 23-06-2020 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/aura-authors/LICENSE b/plugins/aura-authors/LICENSE new file mode 100644 index 0000000..c936675 --- /dev/null +++ b/plugins/aura-authors/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Matt Mulhall + +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/plugins/aura-authors/README.md b/plugins/aura-authors/README.md new file mode 100644 index 0000000..da9106b --- /dev/null +++ b/plugins/aura-authors/README.md @@ -0,0 +1,39 @@ +# Aura Authors Plugin + +The **Aura Authors** Plugin for [Grav CMS](https://github.com/getgrav/grav) enables you to store author bios in a centrally managed repository and have them displayed across various pages of your site. + +![Aura Authors Plugin for Grav - Demo](assets/demo.jpg) + +## Features + +* Easily manage author bios via the Grav admin interface +* Central repository ensures that a single change to an author's bio will automatically update it across multiple pages +* Optionally include author's image and links to social media accounts such as Twitter, LinkedIn etc. +* Use the included mobile and desktop responsive styling or provide your own +* Supports `author` taxonomy type for page collections based on author's individual taxonomy label + +## Installation + +It is recommended to install Aura Authors directly through the Admin Plugin by browsing to the `Plugins` tab and selecting `Add`. + +## Configuration + +* Enter author details via the Admin Plugin by browsing to `Plugins` > `Aura Authors` and selecting `Add Item`. + +* Copy the following code snippet to the relevant Twig template within your theme, at the place where you would like the author bio to be displayed. + +``` + {% include 'partials/author-bio.html.twig' ignore missing %} +``` + +* Once an author is defined at the page level (see Usage below) the relevant author bio will now be displayed on that page (provided it uses the template updated above). Alternatively if you do not have access or do not wish to edit the theme you can include the code snippet directly within a page via the page editor. For this option to work you will need ensure Twig processing is enabled either at the page level (`Page Editor` > `Advanced` > `Overrides` > `Process`) or the site level (`Configuration` > `System` > `Content` > `Process`). + +* Optionally customise the layout of the author bio by copying the included file `templates/partials/author-bio.html.twig` into the same location under your theme and editing it. Default styling can be disabled via the Plugin configuration panel if you wish to provide your own. + +## Usage + +Authors can be selected per page via the page editor, on the `Aura` tab. The list of authors will be automatically populated with author records you create via the Plugins panel. + +## Credits + +Includes a subset of the [IcoMoon - Free](https://icomoon.io/#icons-icomoon) icon pack. \ No newline at end of file diff --git a/plugins/aura-authors/assets/demo.jpg b/plugins/aura-authors/assets/demo.jpg new file mode 100644 index 0000000..da08566 Binary files /dev/null and b/plugins/aura-authors/assets/demo.jpg differ diff --git a/plugins/aura-authors/assets/fonts/icomoon.eot b/plugins/aura-authors/assets/fonts/icomoon.eot new file mode 100644 index 0000000..8e5ba2b Binary files /dev/null and b/plugins/aura-authors/assets/fonts/icomoon.eot differ diff --git a/plugins/aura-authors/assets/fonts/icomoon.svg b/plugins/aura-authors/assets/fonts/icomoon.svg new file mode 100644 index 0000000..3e569cb --- /dev/null +++ b/plugins/aura-authors/assets/fonts/icomoon.svg @@ -0,0 +1,17 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/aura-authors/assets/fonts/icomoon.ttf b/plugins/aura-authors/assets/fonts/icomoon.ttf new file mode 100644 index 0000000..ae57779 Binary files /dev/null and b/plugins/aura-authors/assets/fonts/icomoon.ttf differ diff --git a/plugins/aura-authors/assets/fonts/icomoon.woff b/plugins/aura-authors/assets/fonts/icomoon.woff new file mode 100644 index 0000000..6a6341f Binary files /dev/null and b/plugins/aura-authors/assets/fonts/icomoon.woff differ diff --git a/plugins/aura-authors/assets/style.min.css b/plugins/aura-authors/assets/style.min.css new file mode 100644 index 0000000..17f0b68 --- /dev/null +++ b/plugins/aura-authors/assets/style.min.css @@ -0,0 +1 @@ +.author-bio{background-color:#f5f5f5;margin:40px 0;padding:25px;border-radius:15px;overflow:hidden}.author-bio .author-heading{font-weight:bold;margin:0;padding:0}.author-bio hr{margin:5px 0 15px;padding:0;}.author-bio .author-image{border-radius:10%;width:128px;height:auto;float:left;margin:30px 25px 15px 0 !important;padding:0 !important}.author-bio .author-name{float:left;margin-bottom:0px}.author-bio .author-name p{font-weight:bolder;margin:0;padding:0}.author-bio .author-social{float:right;margin-bottom:0px}.author-bio .author-social ul{margin:0;padding:0;list-style:none}.author-bio .author-social ul li{display:inline}.author-bio .author-social ul li:not(:first-child){padding-left:10px}.author-bio .author-social ul li a,.author-bio .author-social ul li a:active,.author-bio .author-social ul li a:focus,.author-bio .author-social ul li a:hover{text-decoration:none}.author-bio .clear-right{clear:right}.author-bio .author-description p{overflow:hidden;margin:0;padding:0}@media only screen and (max-width: 767px){.author-bio .author-description p{overflow:unset}}@media only screen and (max-width: 575px){.author-bio .author-name,.author-bio .author-social{float:unset}}@font-face{font-family:"icomoon";src:url("fonts/icomoon.eot?aizbyq");src:url("fonts/icomoon.eot?aizbyq#iefix") format("embedded-opentype"),url("fonts/icomoon.ttf?aizbyq") format("truetype"),url("fonts/icomoon.woff?aizbyq") format("woff"),url("fonts/icomoon.svg?aizbyq#icomoon") format("svg");font-weight:normal;font-style:normal;font-display:block}[class^=aura-icon-],[class*=" aura-icon-"]{font-family:"icomoon" !important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.aura-icon-website:before{content:""}.aura-icon-facebook:before{content:""}.aura-icon-instagram:before{content:""}.aura-icon-twitter:before{content:""}.aura-icon-youtube:before{content:""}.aura-icon-linkedin:before{content:""}.aura-icon-pinterest:before{content:""}/*# sourceMappingURL=style.min.css.map */ diff --git a/plugins/aura-authors/assets/style.min.css.map b/plugins/aura-authors/assets/style.min.css.map new file mode 100644 index 0000000..ff9fb45 --- /dev/null +++ b/plugins/aura-authors/assets/style.min.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAAA,YACE,yBACA,cACA,aACA,mBACA,gBACA,4BACE,iBACA,SACA,UAEF,eACE,kBACA,UAEF,0BACE,kBACA,YACA,YACA,WACA,gCACA,qBAEF,yBACE,WACA,mBACA,2BACE,mBACA,SACA,UAGJ,2BACE,YACA,mBACA,8BACE,SACA,UACA,gBACA,iCACE,eACA,mDACE,kBAEF,+JACE,qBAKR,yBACE,YAGA,kCACE,gBACA,SACA,UAKN,0CAGM,kCACE,gBAMR,0CAEI,oDACE,aAKN,WACE,sBACA,oCACA,+NAIA,mBACA,kBACA,mBAGF,2CAEE,iCACA,WACA,kBACA,mBACA,oBACA,oBACA,cAGA,mCACA,kCAGF,0BACE,YAEF,2BACE,YAEF,4BACE,YAEF,0BACE,YAEF,0BACE,YAEF,2BACE,YAEF,4BACE","file":"style.min.css"} \ No newline at end of file diff --git a/plugins/aura-authors/assets/style.scss b/plugins/aura-authors/assets/style.scss new file mode 100644 index 0000000..b6ea1fa --- /dev/null +++ b/plugins/aura-authors/assets/style.scss @@ -0,0 +1,128 @@ +.author-bio { + background-color: #F5F5F5; + margin: 40px 0; + padding: 25px; + border-radius: 15px; + overflow: hidden; + .author-heading { + font-weight: bold; + margin: 0; + padding: 0; + } + hr { + margin: 5px 0 15px; + padding: 0; + } + .author-image { + border-radius: 50%; + width: 128px; + height: auto; + float: left; + margin: 0 25px 15px 0 !important; + padding: 0 !important; + } + .author-name { + float: left; + margin-bottom: 10px; + p { + font-weight: bolder; + margin: 0; + padding: 0; + } + } + .author-social { + float: right; + margin-bottom: 10px; + ul { + margin: 0; + padding: 0; + list-style: none; + li { + display: inline; + &:not(:first-child) { + padding-left: 10px; + } + a, a:active, a:focus, a:hover { + text-decoration: none; + } + } + } + } + .clear-right { + clear: right; + } + .author-description { + p { + overflow: hidden; + margin: 0; + padding: 0; + } + } +} + +@media only screen and (max-width : 767px) { + .author-bio { + .author-description { + p { + overflow: unset; + } + } + } +} + +@media only screen and (max-width : 575px) { + .author-bio { + .author-name, .author-social { + float: unset; + } + } +} + +@font-face { + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?aizbyq'); + src: url('fonts/icomoon.eot?aizbyq#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?aizbyq') format('truetype'), + url('fonts/icomoon.woff?aizbyq') format('woff'), + url('fonts/icomoon.svg?aizbyq#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="aura-icon-"], [class*=" aura-icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.aura-icon-website:before { + content: "\e9cb"; +} +.aura-icon-facebook:before { + content: "\ea90"; +} +.aura-icon-instagram:before { + content: "\ea92"; +} +.aura-icon-twitter:before { + content: "\ea96"; +} +.aura-icon-youtube:before { + content: "\ea9d"; +} +.aura-icon-linkedin:before { + content: "\eaca"; +} +.aura-icon-pinterest:before { + content: "\ead2"; +} \ No newline at end of file diff --git a/plugins/aura-authors/aura-authors.php b/plugins/aura-authors/aura-authors.php new file mode 100644 index 0000000..8d9c372 --- /dev/null +++ b/plugins/aura-authors/aura-authors.php @@ -0,0 +1,112 @@ +isAdmin()) { + $this->enable([ + 'onGetPageBlueprints' => ['onGetPageBlueprints', 0], + 'onAdminSave' => ['onAdminSave', 0], + ]); + return; + } + + // Frontend events + $this->enable([ + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + ]); + + } + + /** + * Add plugin directory to twig lookup paths + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Extend page blueprints with additional configuration options. + * + * @param Event $event + */ + public function onGetPageBlueprints($event) + { + + self::$authors = $this->grav['config']->get('plugins.aura-authors.authors'); + + $types = $event->types; + $types->scanBlueprints('plugins://' . $this->name . '/blueprints'); + } + + public function onAdminSave(Event $event) + { + // Don't proceed if Admin is not saving a Page + if (!$event['object'] instanceof Page) { + return; + } + + $page = $event['object']; + if (isset($page->header()->aura['author'])) { + + // TODO: tidy this section + // Also consider how to remove an author (currently need to go to expert mode) + // Also what if someone wants to set multiple author tags? should proably allow but only consider the aura one for meta output + if (isset($page->header()->taxonomy)) { + $taxonomy = $page->header()->taxonomy; + } else { + $taxonomy = []; + } + $taxonomy['author'] = array($page->header()->aura['author']); + $page->header()->taxonomy = $taxonomy; + } + } + + public static function listAuthors() { + $authorList = []; + $authors = self::getAuthors(); + foreach ($authors as $author) { + $authorList[$author['label']] = $author['name']; + } + asort($authorList); + return $authorList; + } + + /** + * Create structured authors array and expose to Twig + */ + public function onTwigSiteVariables() + { + $authors = array(); + $raw = $this->grav['config']->get('plugins.aura-authors.authors'); + if ($raw) { + foreach ($raw as $author) { + $authors[$author['label']] = $author; + } + } + $this->grav['twig']->twig_vars['authors'] = $authors; + } + +} \ No newline at end of file diff --git a/plugins/aura-authors/aura-authors.yaml b/plugins/aura-authors/aura-authors.yaml new file mode 100644 index 0000000..2a0ca0e --- /dev/null +++ b/plugins/aura-authors/aura-authors.yaml @@ -0,0 +1,2 @@ +enabled: true +include-css: true diff --git a/plugins/aura-authors/blueprints.yaml b/plugins/aura-authors/blueprints.yaml new file mode 100755 index 0000000..2f17948 --- /dev/null +++ b/plugins/aura-authors/blueprints.yaml @@ -0,0 +1,126 @@ +name: Aura Authors +version: 1.0.0 +description: Store author bios in a central repository for display on multiple pages. +icon: users +author: + name: Matt Mulhall + email: matt@theskylab.net + url: https://www.theskylab.net +homepage: https://github.com/matt-j-m/grav-plugin-aura-authors +keywords: author, bio, blog +bugs: https://github.com/matt-j-m/grav-plugin-aura-authors/issues +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + include-css: + type: toggle + label: Include default CSS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + authors: + type: list + label: Author details + help: "Add or edit author details" + fields: + .name: + type: text + size: large + label: Name + validate: + required: true + .label: + type: text + size: large + label: Taxonomy Label + validate: + pattern: "[a-z][a-zA-Z0-9]*$" + message: "Use all lowercase letters and replace spaces with hyphens." + required: true + .image: + type: file + size: large + label: Image + multiple: false + destination: 'user/images' + accept: + - image/* + .description: + type: textarea + size: long + label: Description + .person-mastodon-url: + type: text + size: large + label: Mastodon URL + placeholder: 'https://fosstodon.org/@techsaviours' + .person-matrix-org-url: + type: text + size: large + label: Matrix URL + placeholder: 'https://chat.techsaviours.org/#/user/@user:techsaviours.org' + .person-gitea-url: + type: text + size: large + label: Gitea URL + placeholder: 'https://git.techsaviours.org/user' + .person-github-url: + type: text + size: large + label: GitHub URL + placeholder: 'https://github.com/user' + .person-envelope-user: + type: text + size: large + label: Email Address + placeholder: 'user@techsaviours.nz' + .person-facebook-url: + type: text + size: large + label: Facebook URL + placeholder: 'https://www.facebook.com/username' + .person-twitter-user: + type: text + size: large + label: Twitter Username + placeholder: 'username' + .person-instagram-url: + type: text + size: large + label: Instagram URL + placeholder: 'https://www.instagram.com/username' + .person-linkedin-url: + type: text + size: large + label: LinkedIn URL + placeholder: 'https://www.linkedin.com/in/name' + .person-pinterest-url: + type: text + size: large + label: Pinterest URL + placeholder: 'https://www.pinterest.com/user/username' + .person-youtube-url: + type: text + size: large + label: YouTube URL + placeholder: 'https://www.youtube.com/username' + .person-globe-url: + type: text + label: Website URL + placeholder: 'https://www.example.com' diff --git a/plugins/aura-authors/blueprints/default.yaml b/plugins/aura-authors/blueprints/default.yaml new file mode 100644 index 0000000..0f95295 --- /dev/null +++ b/plugins/aura-authors/blueprints/default.yaml @@ -0,0 +1,40 @@ +title: Aura +'@extends': + type: default + context: blueprints://pages + +form: + fields: + tabs: + type: tabs + active: 1 + + fields: + + options: + type: tab + title: PLUGIN_ADMIN.OPTIONS + + fields: + publishing: + type: section + title: PLUGIN_ADMIN.PUBLISHING + underline: true + + fields: + header.metadata: + unset@: true + + aura: + type: tab + title: Aura + + fields: + + header.aura.author: + type: select + label: Author + size: medium + data-options@: '\Grav\Plugin\AuraAuthorsPlugin::listAuthors' + options: + '': '' diff --git a/plugins/aura-authors/templates/partials/author-bio.html.twig b/plugins/aura-authors/templates/partials/author-bio.html.twig new file mode 100644 index 0000000..75af7aa --- /dev/null +++ b/plugins/aura-authors/templates/partials/author-bio.html.twig @@ -0,0 +1,45 @@ +{% set author = authors[page.header.aura.author] %} +{% if author %} + {% set social = [ + 'linkedin', + 'youtube', + 'facebook', + 'instagram', + 'pinterest', + 'website', + ] %} +
    +

    About the author

    +
    + {% if author.image %} + {{ media['user://images/' ~ author.image|first.name].html('', author.name, 'author-image') }} + {% endif %} +
    +
    +
    +

    {{ author.name }}

    +
    +
    +
      + {% for item in social %} + {% set href = author['person-' ~ item ~ '-url'] %} + {% if href %} +
    • + {% endif %} + {% endfor %} + {% if author['person-twitter-user'] %} +
    • + {% endif %} +
      +
    +
    +
    +
    +

    {{ author.description }}

    +
    +
    +
    + {% if config.plugins['aura-authors']['include-css'] %} + {% do assets.addCss('user://plugins/aura-authors/assets/style.min.css') %} + {% endif %} +{% endif %} \ No newline at end of file diff --git a/plugins/comments/CHANGELOG.md b/plugins/comments/CHANGELOG.md new file mode 100644 index 0000000..ee6a5d8 --- /dev/null +++ b/plugins/comments/CHANGELOG.md @@ -0,0 +1,135 @@ +# v1.2.8 +## 09/10/2020 + +1. [](#improved) + * Improved some translations +1. [](#bugfix) + * Fix for PHP 7.4 [#81](https://github.com/getgrav/grav-plugin-comments/issues/81) + * Fix for allowing path in form submission [#86](https://github.com/getgrav/grav-plugin-comments/issues/86) [#80](https://github.com/getgrav/grav-plugin-comments/issues/80) + +# v1.2.7 +## 05/12/2017 + +1. [](#improved) + * Added Japanese translation + * Move captcha over email [#45](https://github.com/getgrav/grav-plugin-comments/issues/45) +1. [](#bugfix) + * Fix comment form processing + * Fix issue with scope for autofilled values + +# v1.2.6 +## 01/09/2017 + +1. [](#improved) + * Use existing `Utils::startsWith()` method +1. [](#bugfix) + * Fix [#41](https://github.com/getgrav/grav-plugin-comments/issues/41) using Comments in a Gantry-powered theme did not escape the comment form token correctly + +# v1.2.5 +## 09/16/2016 + +1. [](#bugfix) + * Fix [#37](https://github.com/getgrav/grav-plugin-comments/issues/37) showing comments older than one week in the "latest comments" view + +# v1.2.4 +## 09/15/2016 + +1. [](#bugfix) + * Fix missing Twig template error if route is excluded but twig is loaded + +# v1.2.3 +## 09/15/2016 + +1. [](#improved) + * Added Croatian translation +1. [](#bugfix) + * Fix [#35](https://github.com/getgrav/grav-plugin-comments/issues/35) Allow comments to work fine on Form 2.0 too + +# v1.2.2 +## 08/12/2016 + +1. [](#improved) + * Added Romanian translation +1. [](#bugfix) + * Fix issue in storing comments cache when cache is enabled [#33](https://github.com/getgrav/grav-plugin-comments/issues/33) + +# v1.2.1 +## 07/19/2016 + +1. [](#bugfix) + * Check if Login plugin is installed before checking for user object [#28](https://github.com/getgrav/grav-plugin-comments/issues/28) + +# v1.2.0 +## 07/14/2016 + +1. [](#improved) + * Prevent a missing template problem on ignored routes + * Allow to translate the comments form + * Added spanish and brazilian portuguese translations + * Enhanced german, russian and french translations + * Added cache for comments + * Handle logged in users by not requiring username/email + * Reset the comments form after a comment is submitted + +# v1.1.4 +## 02/05/2016 + +1. [](#improved) + * Added german and polish + * Avoid listening on onTwigTemplatePaths if not enabled + +# v1.1.3 +## 01/06/2016 + +1. [](#improved) + * Disable captcha by default, added instructions on how to enable it +1. [](#bugfix) + * Increase priority for onPageInitialized in the comments plugin over the form plugin one to prevent an issue when saving comments + +# v1.1.2 +## 12/11/2015 + +1. [](#improved) + Fix double escaping comments text and author + +# v1.1.1 +## 12/11/2015 + +1. [](#improved) + * Drop the autofocus on the comment form +1. [](#bugfix) + * Fix double encoding (#12) + +# v1.1.0 +## 11/24/2015 + +1. [](#new) + * Added french (@codebee-fr) and russian (@joomline) languages + * Takes advantage of the new nonce support provided by the Form plugin +1. [](#improved) + * Use date instead of gmdate to respect the server local time (thanks @bovisp) + * Now works with multilang (thanks @bovisp) + + +# v1.0.2 +## 11/13/2015 + +1. [](#improved) + * Use nonce +1. [](#improved) + * Changed form action to work with multilang + +# v1.0.1 +## 11/11/2015 + +1. [](#improved) + * Use onAdminMenu instead of the deprecated onAdminTemplateNavPluginHook +1. [](#bugfix) + * Fix error when user/data/comments does not exist + + +# v1.0.0 +## 10/21/2015 + +1. [](#new) + * Initial Release diff --git a/plugins/comments/README.md b/plugins/comments/README.md new file mode 100644 index 0000000..a403d84 --- /dev/null +++ b/plugins/comments/README.md @@ -0,0 +1,74 @@ +# Grav Comments Plugin + +The **Comments Plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to add comments to pages, and moderate them. + +# Installation + +The Comments plugin is easy to install with GPM. + +``` +$ bin/gpm install comments +``` + +Or clone from GitHub and put in the `user/plugins/comments` folder. + +# Usage + +Add `{% include 'partials/comments.html.twig' with {'page': page} %}` to the template file where you want to add comments. + +For example, in Antimatter, in `templates/item.html.twig`: + +```twig +{% embed 'partials/base.html.twig' %} + + {% block content %} + {% if config.plugins.breadcrumbs.enabled %} + {% include 'partials/breadcrumbs.html.twig' %} + {% endif %} + +
    +
    + {% include 'partials/blog_item.html.twig' with {'blog':page.parent, 'truncate':false} %} +
    + +
    + + {% include 'partials/comments.html.twig' with {'page': page} %} + {% endblock %} + +{% endembed %} +``` + +The comment form will appear on the blog post items matching the enabled routes. + +To set the enabled routes, create a `user/config/plugins/comments.yaml` file, copy in it the contents of `user/plugins/comments/comments.yaml` and edit the `enable_on_routes` and `disable_on_routes` options according to your needs. + +> Make sure you configured the "Email from" and "Email to" email addresses in the Email plugin with your email address! + +# Enabling Recaptcha + +The plugin comes with Recaptcha integration. To make it work, create a `user/config/plugins/comments.yaml` file, copy in it the contents of `user/plugins/comments/comments.yaml` and uncomment the captcha form field and the captcha validation process. +Make sure you add your own Recaptcha `site` and `secret` keys too. + +# Where are the comments stored? + +In the `user/data/comments` folder. They're organized by page route, so every page with a comment has a corresponding file. This enables a quick load of all the page comments. + +# Visualize comments + +When the plugin is installed and enabled, the `Comments` menu will appear in the Admin Plugin. From there you can see all the comments made in the last 7 days. + +Further improvements to the comments visualization will be added in the next releases. + +# Email notifications + +The plugin interacts with the Email plugin to send emails upon receiving a comment. Configure the Email plugin correctly, setting its "Email from" and "Email to" email addresses. + +# Things still missing + +- Allow to delete comments from the Admin Plugin +- Ability to see all comments of a page in the Admin Plugin +- Ability to reply to a comment from the Admin Plugin +- Auto-fill the comment form when a user is logged in diff --git a/plugins/comments/admin/pages/comments.md b/plugins/comments/admin/pages/comments.md new file mode 100644 index 0000000..2b290b0 --- /dev/null +++ b/plugins/comments/admin/pages/comments.md @@ -0,0 +1,7 @@ +--- +title: Comments + +access: + admin.comments: true + admin.super: true +--- diff --git a/plugins/comments/admin/templates/comments.html.twig b/plugins/comments/admin/templates/comments.html.twig new file mode 100644 index 0000000..6fea510 --- /dev/null +++ b/plugins/comments/admin/templates/comments.html.twig @@ -0,0 +1,133 @@ +{% extends 'partials/base.html.twig' %} + +{% if admin.route %} + {% set context = admin.page(true) %} +{% endif %} + +{% block titlebar %} +

    {{ "PLUGIN_COMMENTS.COMMENTS"|tu }}

    +{% endblock %} + +{% block content %} + + + + +

    Comments in the last 7 days

    + +
    + + + + + + + + {% for comment in grav.twig.comments.comments %} + + + + + + {% endfor %} + +
    AuthorCommentDetails
    {{comment.author}}{{comment.text}}Page: {{comment.pageTitle}}
    + Date: {{comment.date}}
    + + {% if grav.twig.comments.totalRetrieved < grav.twig.comments.totalAvailable %} + + {% endif %} + +

    Showing {{grav.twig.comments.totalRetrieved}} comments of {{grav.twig.comments.totalAvailable}}

    +
    + + {% if grav.twig.pages %} +

    Recently commented pages

    + +
    + + + + + + + + {% for page in grav.twig.pages %} + + + + + + {% endfor %} + +
    PageNumber of commentsLast commented on
    {{page.title}}{{page.commentsCount}}{{page.lastCommentDate}}
    +
    + {% endif %} + +{% endblock %} + + diff --git a/plugins/comments/blueprints.yaml b/plugins/comments/blueprints.yaml new file mode 100644 index 0000000..4b825e8 --- /dev/null +++ b/plugins/comments/blueprints.yaml @@ -0,0 +1,33 @@ +name: Comments +type: plugin +slug: comments +version: 1.2.8 +description: Adds a commenting functionality to your site +icon: comment +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-comments +keywords: guestbook, plugin +bugs: https://github.com/getgrav/grav-plugin-comments/issues +readme: https://github.com/getgrav/grav-plugin-comments/blob/develop/README.md +license: MIT + +dependencies: + - form + - email + +form: + validation: loose + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/plugins/comments/comments.php b/plugins/comments/comments.php new file mode 100644 index 0000000..f7f44eb --- /dev/null +++ b/plugins/comments/comments.php @@ -0,0 +1,435 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize form if the page has one. Also catches form processing if user posts the form. + * + * Used by Form plugin < 2.0, kept for backwards compatibility + * + * @deprecated + */ + public function onPageInitialized() + { + /** @var Page $page */ + $page = $this->grav['page']; + if (!$page) { + return; + } + + if ($this->enable) { + $header = $page->header(); + if (!isset($header->form)) { + $header->form = $this->grav['config']->get('plugins.comments.form'); + $page->header($header); + } + } + } + + /** + * Add the comment form information to the page header dynamically + * + * Used by Form plugin >= 2.0 + */ + public function onFormPageHeaderProcessed(Event $event) + { + $header = $event['header']; + + if ($this->enable) { + if (!isset($header->form)) { + $header->form = $this->grav['config']->get('plugins.comments.form'); + } + } + + $event->header = $header; + } + + public function onTwigSiteVariables() { + // Old way + $enabled = $this->enable; + $comments = $this->fetchComments(); + + $this->grav['twig']->enable_comments_plugin = $enabled; + $this->grav['twig']->comments = $comments; + + // New way + $this->grav['twig']->twig_vars['enable_comments_plugin'] = $enabled; + $this->grav['twig']->twig_vars['comments'] = $comments; + + } + + /** + * Determine if the plugin should be enabled based on the enable_on_routes and disable_on_routes config options + */ + private function calculateEnable() { + $uri = $this->grav['uri']; + + $disable_on_routes = (array) $this->config->get('plugins.comments.disable_on_routes'); + $enable_on_routes = (array) $this->config->get('plugins.comments.enable_on_routes'); + + $path = $uri->path(); + + if (!in_array($path, $disable_on_routes)) { + if (in_array($path, $enable_on_routes)) { + $this->enable = true; + } else { + foreach($enable_on_routes as $route) { + if (Utils::startsWith($path, $route)) { + $this->enable = true; + break; + } + } + } + } + } + + /** + * Frontend side initialization + */ + public function initializeFrontend() + { + $this->calculateEnable(); + + $this->enable([ + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + ]); + + if ($this->enable) { + $this->enable([ + 'onFormProcessed' => ['onFormProcessed', 0], + 'onFormPageHeaderProcessed' => ['onFormPageHeaderProcessed', 0], + 'onPageInitialized' => ['onPageInitialized', 10], + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] + ]); + } + + $cache = $this->grav['cache']; + $uri = $this->grav['uri']; + + //init cache id + $this->comments_cache_id = md5('comments-data' . $cache->getKey() . '-' . $uri->url()); + } + + /** + * Admin side initialization + */ + public function initializeAdmin() + { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + + $this->enable([ + 'onTwigTemplatePaths' => ['onTwigAdminTemplatePaths', 0], + 'onAdminMenu' => ['onAdminMenu', 0], + 'onDataTypeExcludeFromDataManagerPluginHook' => ['onDataTypeExcludeFromDataManagerPluginHook', 0], + ]); + + if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $this->route) === false) { + return; + } + + $page = $this->grav['uri']->param('page'); + $comments = $this->getLastComments($page); + + if ($page > 0) { + echo json_encode($comments); + exit(); + } + + $this->grav['twig']->comments = $comments; + $this->grav['twig']->pages = $this->fetchPages(); + } + + /** + */ + public function onPluginsInitialized() + { + if ($this->isAdmin()) { + $this->initializeAdmin(); + } else { + $this->initializeFrontend(); + } + } + + /** + * Handle form processing instructions. + * + * @param Event $event + */ + public function onFormProcessed(Event $event) + { + $form = $event['form']; + $action = $event['action']; + $params = $event['params']; + + if (!$this->active) { + return; + } + + switch ($action) { + case 'addComment': + $post = isset($_POST['data']) ? $_POST['data'] : []; + + $path = $this->grav['uri']->path(); + + $lang = filter_var(urldecode($post['lang']), FILTER_SANITIZE_STRING); + $text = filter_var(urldecode($post['text']), FILTER_SANITIZE_STRING); + $name = filter_var(urldecode($post['name']), FILTER_SANITIZE_STRING); + $email = filter_var(urldecode($post['email']), FILTER_SANITIZE_STRING); + $title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING); + + if (isset($this->grav['user'])) { + $user = $this->grav['user']; + if ($user->authenticated) { + $name = $user->fullname; + $email = $user->email; + } + } + + /** @var Language $language */ + $language = $this->grav['language']; + $lang = $language->getLanguage(); + + $filename = DATA_DIR . 'comments'; + $filename .= ($lang ? '/' . $lang : ''); + $filename .= $path . '.yaml'; + $file = File::instance($filename); + + if (file_exists($filename)) { + $data = Yaml::parse($file->content()); + + $data['comments'][] = [ + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]; + } else { + $data = array( + 'title' => $title, + 'lang' => $lang, + 'comments' => array([ + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]) + ); + } + + $file->save(Yaml::dump($data)); + + //clear cache + $this->grav['cache']->delete($this->comments_cache_id); + + break; + } + } + + private function getFilesOrderedByModifiedDate($path = '') { + $files = []; + + if (!$path) { + $path = DATA_DIR . 'comments'; + } + + if (!file_exists($path)) { + Folder::mkdir($path); + } + + $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + $filterItr = new RecursiveFolderFilterIterator($dirItr); + $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST); + + $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); + $filesItr = new \RegexIterator($itrItr, '/^.+\.yaml$/i'); + + // Collect files if modified in the last 7 days + foreach ($filesItr as $filepath => $file) { + $modifiedDate = $file->getMTime(); + $sevenDaysAgo = time() - (7 * 24 * 60 * 60); + + if ($modifiedDate < $sevenDaysAgo) { + continue; + } + + $files[] = (object)array( + "modifiedDate" => $modifiedDate, + "fileName" => $file->getFilename(), + "filePath" => $filepath, + "data" => Yaml::parse(file_get_contents($filepath)) + ); + } + + // Traverse folders and recurse + foreach ($itr as $file) { + if ($file->isDir()) { + $this->getFilesOrderedByModifiedDate($file->getPath() . '/' . $file->getFilename()); + } + } + + // Order files by last modified date + usort($files, function($a, $b) { + return !($a->modifiedDate > $b->modifiedDate); + }); + + return $files; + } + + private function getLastComments($page = 0) { + $number = 30; + + $files = []; + $files = $this->getFilesOrderedByModifiedDate(); + $comments = []; + + foreach($files as $file) { + $data = Yaml::parse(file_get_contents($file->filePath)); + + for ($i = 0; $i < count($data['comments']); $i++) { + $commentTimestamp = \DateTime::createFromFormat('D, d M Y H:i:s', $data['comments'][$i]['date'])->getTimestamp(); + + $data['comments'][$i]['pageTitle'] = $data['title']; + $data['comments'][$i]['filePath'] = $file->filePath; + $data['comments'][$i]['timestamp'] = $commentTimestamp; + } + if (count($data['comments'])) { + $comments = array_merge($comments, $data['comments']); + } + } + + // Order comments by date + usort($comments, function($a, $b) { + return !($a['timestamp'] > $b['timestamp']); + }); + + $totalAvailable = count($comments); + $comments = array_slice($comments, $page * $number, $number); + $totalRetrieved = count($comments); + + return (object)array( + "comments" => $comments, + "page" => $page, + "totalAvailable" => $totalAvailable, + "totalRetrieved" => $totalRetrieved + ); + } + + /** + * Return the comments associated to the current route + */ + private function fetchComments() { + $cache = $this->grav['cache']; + //search in cache + if ($comments = $cache->fetch($this->comments_cache_id)) { + return $comments; + } + + $lang = $this->grav['language']->getLanguage(); + $filename = $lang ? '/' . $lang : ''; + $filename .= $this->grav['uri']->path() . '.yaml'; + + $data = $this->getDataFromFilename($filename); + $comments = isset($data['comments']) ? $data['comments'] : null; + //save to cache if enabled + $cache->save($this->comments_cache_id, $comments); + return $comments; + } + + /** + * Return the latest commented pages + */ + private function fetchPages() { + $files = []; + $files = $this->getFilesOrderedByModifiedDate(); + + $pages = []; + + foreach($files as $file) { + $pages[] = [ + 'title' => $file->data['title'], + 'commentsCount' => count($file->data['comments']), + 'lastCommentDate' => date('D, d M Y H:i:s', $file->modifiedDate) + ]; + } + + return $pages; + } + + + /** + * Given a data file route, return the YAML content already parsed + */ + private function getDataFromFilename($fileRoute) { + + //Single item details + $fileInstance = File::instance(DATA_DIR . 'comments/' . $fileRoute); + + if (!$fileInstance->content()) { + //Item not found + return; + } + + return Yaml::parse($fileInstance->content()); + } + + /** + * Add templates directory to twig lookup paths. + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Add plugin templates path + */ + public function onTwigAdminTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates'; + } + + /** + * Add navigation item to the admin plugin + */ + public function onAdminMenu() + { + $this->grav['twig']->plugins_hooked_nav['PLUGIN_COMMENTS.COMMENTS'] = ['route' => $this->route, 'icon' => 'fa-file-text']; + } + + /** + * Exclude comments from the Data Manager plugin + */ + public function onDataTypeExcludeFromDataManagerPluginHook() + { + $this->grav['admin']->dataTypesExcludedFromDataManagerPlugin[] = 'comments'; + } +} \ No newline at end of file diff --git a/plugins/comments/comments.yaml b/plugins/comments/comments.yaml new file mode 100644 index 0000000..5c09966 --- /dev/null +++ b/plugins/comments/comments.yaml @@ -0,0 +1,77 @@ +enabled: true + +enable_on_routes: + - '/blog' + +disable_on_routes: + - /blog/blog-post-to-ignore + - /ignore-this-route + #- '/blog/daring-fireball-link' + +form: + name: comments + fields: + - name: name + label: PLUGIN_COMMENTS.NAME_LABEL + placeholder: PLUGIN_COMMENTS.NAME_PLACEHOLDER + autocomplete: on + type: text + validate: + required: true + + - name: email + label: PLUGIN_COMMENTS.EMAIL_LABEL + placeholder: PLUGIN_COMMENTS.EMAIL_PLACEHOLDER + type: email + validate: + required: true + + - name: text + label: PLUGIN_COMMENTS.MESSAGE_LABEL + placeholder: PLUGIN_COMMENTS.MESSAGE_PLACEHOLDER + type: textarea + validate: + required: true + + - name: date + type: hidden + process: + fillWithCurrentDateTime: true + + - name: title + type: hidden + evaluateDefault: grav.page.header.title + + - name: lang + type: hidden + evaluateDefault: grav.language.getLanguage + + - name: path + type: hidden + evaluateDefault: grav.uri.path + +# - name: g-recaptcha-response +# label: Captcha +# type: captcha +# recaptcha_site_key: e32iojeoi32jeoi32jeoij32oiej32oiej3 +# recaptcha_not_validated: 'Captcha not valid!' +# validate: +# required: true +# process: +# ignore: true + + buttons: + - type: submit + value: PLUGIN_COMMENTS.SUBMIT_COMMENT_BUTTON_TEXT + + process: +# - captcha: +# recaptcha_secret: ej32oiej23oiej32oijeoi32jeio32je + - email: + subject: PLUGIN_COMMENTS.EMAIL_NEW_COMMENT_SUBJECT + body: "{% include 'forms/data.html.twig' %}" + - addComment: + - message: PLUGIN_COMMENTS.THANK_YOU_MESSAGE + - reset: true + + diff --git a/plugins/comments/languages.yaml b/plugins/comments/languages.yaml new file mode 100644 index 0000000..3101de2 --- /dev/null +++ b/plugins/comments/languages.yaml @@ -0,0 +1,276 @@ +en: + PLUGIN_COMMENTS: + ADD_COMMENT: Add a comment + COMMENTS: Comments + EMAIL_NOT_CONFIGURED: Email not configured + NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s' + NEW_COMMENT_EMAIL_BODY: '

    A new comment was made on %1$s by %3$s (%4$s).

    Page: %2$s

    Text: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Name: + EMAIL: Email: + WRITTEN_ON: Written on + BY: by + NAME_LABEL: "Name" + NAME_PLACEHOLDER: "Enter your name" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Enter your email address" + MESSAGE_LABEL: "Comment" + MESSAGE_PLACEHOLDER: "Enter your comment" + SUBMIT_COMMENT_BUTTON_TEXT: "Submit" + EMAIL_NEW_COMMENT_SUBJECT: "[New Comment] from {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Thank you for writing your comment!" + +de: + PLUGIN_COMMENTS: + ADD_COMMENT: Kommentar hinzufügen + COMMENTS: Kommentare + EMAIL_NOT_CONFIGURED: Email nicht konfiguriert + NEW_COMMENT_EMAIL_SUBJECT: 'Neuer Kommentar für %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Ein neuer Kommentar am %1$s von %3$s (%4$s).

    Seite: %2$s

    Text: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Name: + EMAIL: Email: + WRITTEN_ON: geschrieben am + BY: von + NAME_LABEL: "Name" + NAME_PLACEHOLDER: "Namen eingeben" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Email-Adresse eingeben" + MESSAGE_LABEL: "Kommentar" + MESSAGE_PLACEHOLDER: "Kommentar eingeben" + SUBMIT_COMMENT_BUTTON_TEXT: "Absenden" + EMAIL_NEW_COMMENT_SUBJECT: "[Neuer Kommentar] von {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Vielen Dank für den Kommentar!" + +es: + PLUGIN_COMMENTS: + ADD_COMMENT: Agregar un comentario + COMMENTS: Comentarios + EMAIL_NOT_CONFIGURED: El Email no está configurado + NEW_COMMENT_EMAIL_SUBJECT: 'Nuevo comentario en %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Un nuevo comentario se hizo en %1$s por %3$s (%4$s).

    Page: %2$s

    Text: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Nombre: + EMAIL: Email: + WRITTEN_ON: Escrito en + BY: por + NAME_LABEL: "Nombre" + NAME_PLACEHOLDER: "Escriba su nombre" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Escriba su email" + MESSAGE_LABEL: "Comentario" + MESSAGE_PLACEHOLDER: "Escriba su comentario" + SUBMIT_COMMENT_BUTTON_TEXT: "Enviar" + EMAIL_NEW_COMMENT_SUBJECT: "[Nuevo comentario] de {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Gracias por escribir su comentario!" + +fr: + PLUGIN_COMMENTS: + ADD_COMMENT: Ajouter un commentaire + COMMENTS: Commentaires + EMAIL_NOT_CONFIGURED: E-mail non configuré + NEW_COMMENT_EMAIL_SUBJECT: 'Nouveau commentaire sur %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Un nouveau commentaire a été publié sur %1$s par %3$s (%4$s).

    Page : %2$s

    Texte : %5$s

    ' + EMAIL_FOOTER: '' + NAME: Nom : + EMAIL: E-mail : + WRITTEN_ON: Écrit le + BY: par + NAME_LABEL: "Nom" + NAME_PLACEHOLDER: "Indiquez votre nom" + EMAIL_LABEL: "E-mail" + EMAIL_PLACEHOLDER: "Indiquez votre adresse e-mail" + MESSAGE_LABEL: "Commentaire" + MESSAGE_PLACEHOLDER: "Rédigez votre commentaire" + SUBMIT_COMMENT_BUTTON_TEXT: "Envoyer" + EMAIL_NEW_COMMENT_SUBJECT: "[Nouveau commentaire] de {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Merci d'avoir rédigé votre commentaire !" + +hr: + PLUGIN_COMMENTS: + ADD_COMMENT: Dodaj komentar + COMMENTS: Komentari + EMAIL_NOT_CONFIGURED: Email adresa nije podešena + NEW_COMMENT_EMAIL_SUBJECT: 'Novi komentar na %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Novi komentar je napisan na %1$s od %3$s (%4$s).

    Stranica:: %2$s

    Tekst: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Ime: + EMAIL: Email: + WRITTEN_ON: Napisano je na + BY: od + NAME_LABEL: "Ime" + NAME_PLACEHOLDER: "Unesite ime" + EMAIL_LABEL: "Email adresa" + EMAIL_PLACEHOLDER: "Unesite email adresu" + MESSAGE_LABEL: "Komentar" + MESSAGE_PLACEHOLDER: "Unesite komentar" + SUBMIT_COMMENT_BUTTON_TEXT: "Pošalji" + EMAIL_NEW_COMMENT_SUBJECT: "[Novi komentar] od {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Hvala Vam što ste napisali svoj komentar!" + +it: + PLUGIN_COMMENTS: + ADD_COMMENT: Aggiungi un commento + COMMENTS: Commenti + EMAIL_NOT_CONFIGURED: Email non configurata + NEW_COMMENT_EMAIL_SUBJECT: 'Nuovo commento su %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Un nuovo commento è stato postato su %1$s da %3$s (%4$s).

    Pagina: %2$s

    Testo: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Nome: + EMAIL: Email: + WRITTEN_ON: Scritto il + BY: da + NAME_LABEL: "Nome" + NAME_PLACEHOLDER: "Inserisci il tuo nome" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Inserisci il tuo indirizzo email" + MESSAGE_LABEL: "Messaggio" + MESSAGE_PLACEHOLDER: "Inserisci il tuo commento" + SUBMIT_COMMENT_BUTTON_TEXT: "Invia" + EMAIL_NEW_COMMENT_SUBJECT: "[Nuovo commento] da {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Grazie per il tuo commento!" + +ja: + PLUGIN_COMMENTS: + ADD_COMMENT: コメントする + COMMENTS: コメント + EMAIL_NOT_CONFIGURED: メールアドレスが設定さていません + NEW_COMMENT_EMAIL_SUBJECT: '%1$sの新しいコメント' + NEW_COMMENT_EMAIL_BODY: '

    %1$sに新しいコメントが%3$sから(%4$s)に書かれました。

    ページ : %2$s

    内容 : %5$s

    ' + EMAIL_FOOTER: '' + NAME: 名前 : + EMAIL: メールアドレス : + WRITTEN_ON: 投稿日時 + BY: By + NAME_LABEL: "名前" + NAME_PLACEHOLDER: "名前を入力してください" + EMAIL_LABEL: "メールアドレス" + EMAIL_PLACEHOLDER: "メールアドレスを入力してください" + MESSAGE_LABEL: "コメント" + MESSAGE_PLACEHOLDER: "コメントを入力してください" + SUBMIT_COMMENT_BUTTON_TEXT: "送信" + EMAIL_NEW_COMMENT_SUBJECT: "[新しいコメント] {{ form.value.name|e }}から" + THANK_YOU_MESSAGE: "コメントありがとうございます!" + +nl: + PLUGIN_COMMENTS: + ADD_COMMENT: Reageer + COMMENTS: Reacties + EMAIL_NOT_CONFIGURED: Email niet geconfigureerd + NEW_COMMENT_EMAIL_SUBJECT: 'Nieuwe reactie op %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Er is een nieuwe reactie gemaakt op %1$s door %3$s (%4$s).

    Pagina: %2$s

    Tekst: %5$s

    ' + EMAIL_FOOTER: '' + NAME: 'Naam:' + EMAIL: 'Email:' + WRITTEN_ON: 'Geschreven op' + BY: 'door' + NAME_LABEL: "Naam" + NAME_PLACEHOLDER: "Vul je naam in" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Vul je emailadres in" + MESSAGE_LABEL: "Reactie" + MESSAGE_PLACEHOLDER: "Vul je reactie in" + SUBMIT_COMMENT_BUTTON_TEXT: "Verstuur" + EMAIL_NEW_COMMENT_SUBJECT: "[Nieuwe reactie] van {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Bedankt voor je reactie!" + +pl: + PLUGIN_COMMENTS: + ADD_COMMENT: Dodaj komentarz + COMMENTS: Komentarzy + EMAIL_NOT_CONFIGURED: Email jest nie skofigurowany + NEW_COMMENT_EMAIL_SUBJECT: 'Nowy komentarz %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Pojawił się nowy komentarz, napisany %1$s przez %3$s (%4$s).

    Strona: %2$s

    Treść: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Imię: + EMAIL: Email: + WRITTEN_ON: Napisany przez + BY: przez + +ru: + PLUGIN_COMMENTS: + ADD_COMMENT: Добавить комментарий + COMMENTS: Комментарии + EMAIL_NOT_CONFIGURED: Email не настроен + NEW_COMMENT_EMAIL_SUBJECT: 'Новый комментарий к %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Новый комментарий на %1$s от %3$s (%4$s).

    Страница: %2$s

    Текст: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Имя: + EMAIL: Email: + WRITTEN_ON: Написан в + BY: от + NAME_LABEL: "Имя" + NAME_PLACEHOLDER: "Введите свое имя" + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Введите свой email адрес" + MESSAGE_LABEL: "Комментарий" + MESSAGE_PLACEHOLDER: "Введите свой комментарий" + SUBMIT_COMMENT_BUTTON_TEXT: "Отправить" + EMAIL_NEW_COMMENT_SUBJECT: "[Новый комментарий] от {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Благодарим за ваш комментарий!" + +pt-br: + PLUGIN_COMMENTS: + ADD_COMMENT: Escreva um comentário + COMMENTS: Comentários + EMAIL_NOT_CONFIGURED: E-mail não configurado + NEW_COMMENT_EMAIL_SUBJECT: 'Novo comentário em %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Um novo comentário foi feito em %1$s por %3$s (%4$s).

    Página: %2$s

    Texto: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Name: + EMAIL: Email: + WRITTEN_ON: Publicado em + BY: por + NAME_LABEL: "Nome" + NAME_PLACEHOLDER: "Escreva seu nome" + EMAIL_LABEL: "E-mail" + EMAIL_PLACEHOLDER: "Escreva seu e-mail. Ex.: seunome@provedor.com.br" + MESSAGE_LABEL: "Comentário" + MESSAGE_PLACEHOLDER: "Escreva seu comentário" + SUBMIT_COMMENT_BUTTON_TEXT: "Enviar" + EMAIL_NEW_COMMENT_SUBJECT: "[Novo comentário] de {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Obrigada por enviar seu comentário!" + +ro: + PLUGIN_COMMENTS: + ADD_COMMENT: 'Adăugați un comentariu' + COMMENTS: 'Comentarii' + EMAIL_NOT_CONFIGURED: 'Adresa de email nu este configurată' + NEW_COMMENT_EMAIL_SUBJECT: 'Comentariu nou pentru %1$s' + NEW_COMMENT_EMAIL_BODY: '

    Un nou comentariu a fost adăugat la %1$s de către %3$s (%4$s).

    Pagină: %2$s

    Text: %5$s

    ' + EMAIL_FOOTER: '' + NAME: 'Nume:' + EMAIL: 'Adresă de email:' + WRITTEN_ON: 'Scris în data de' + BY: 'de către' + NAME_LABEL: "Numele" + NAME_PLACEHOLDER: "Introduceți numele Dvs." + EMAIL_LABEL: "Email" + EMAIL_PLACEHOLDER: "Introduceți adresa Dvs. de email" + MESSAGE_LABEL: "Comentariu" + MESSAGE_PLACEHOLDER: "Scrieți comentariul Dvs." + SUBMIT_COMMENT_BUTTON_TEXT: "Trimiteți" + EMAIL_NEW_COMMENT_SUBJECT: "[Comentariu nou] from {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Vă mulțumim pentru comentariu!" + +no: + PLUGIN_COMMENTS: + ADD_COMMENT: Skriv en kommentar + COMMENTS: Kommentarer + EMAIL_NOT_CONFIGURED: Epost er ikke konfigurert + NEW_COMMENT_EMAIL_SUBJECT: 'Ny kommentar på %1$s' + NEW_COMMENT_EMAIL_BODY: '

    En ny kommentar er skrevet på %1$s av %3$s (%4$s).

    Side: %2$s

    Tekst: %5$s

    ' + EMAIL_FOOTER: '' + NAME: Navn: + EMAIL: Epost: + WRITTEN_ON: Skrevet på + BY: av + NAME_LABEL: "Navn" + NAME_PLACEHOLDER: "Skriv ditt navn" + EMAIL_LABEL: "Epost" + EMAIL_PLACEHOLDER: "Skriv din epost adresse" + MESSAGE_LABEL: "Kommentar" + MESSAGE_PLACEHOLDER: "Skriv din kommentar" + SUBMIT_COMMENT_BUTTON_TEXT: "Send" + EMAIL_NEW_COMMENT_SUBJECT: "[Ny kommentar] fra {{ form.value.name|e }}" + THANK_YOU_MESSAGE: "Takk for din kommentar!" diff --git a/plugins/comments/templates/partials/comments.html.twig b/plugins/comments/templates/partials/comments.html.twig new file mode 100644 index 0000000..b1ff904 --- /dev/null +++ b/plugins/comments/templates/partials/comments.html.twig @@ -0,0 +1,60 @@ +{% if enable_comments_plugin %} + {% set scope = scope ?: 'data.' %} + +

    {{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}

    + +
    + + {% for field in grav.config.plugins.comments.form.fields %} + {% set value = form.value(field.name) %} + {% if field.evaluateDefault %} + {% set value = evaluate(field.evaluateDefault) %} + {% endif %} + {% if config.plugins.login.enabled and grav.user.authenticated %} + {% if field.name == 'name' %} + + {% elseif field.name == 'email' %} + + {% else %} +
    + {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} +
    + {% endif %} + {% else %} +
    + {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} +
    + {% endif %} + {% endfor %} + {% include "forms/fields/formname/formname.html.twig" %} + +
    + {% for button in grav.config.plugins.comments.form.buttons %} + + {% endfor %} +
    + + {{ nonce_field('form', 'form-nonce')|raw }} +
    + +
    {{ form.message }}
    + + {% if grav.twig.comments|length %} + +

    {{'PLUGIN_COMMENTS.COMMENTS'|t}}

    + + + {% for comment in comments|array_reverse %} + + + + {% endfor %} +
    + {{comment.text}} +
    + {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}} +
    + {% endif %} +{% endif %} diff --git a/plugins/custom-css/CHANGELOG.md b/plugins/custom-css/CHANGELOG.md new file mode 100644 index 0000000..90b6779 --- /dev/null +++ b/plugins/custom-css/CHANGELOG.md @@ -0,0 +1,25 @@ +# v0.2.2 +## 12/19/2018 + +1. [](#new) + * Added translation +1. [](#improved) + * Use Codemirror for Inline CSS + +# v0.2.1 +## 07/27/2016 + +1. [](#bugfix) + * Fix running plugin without adding CSS files to include [#2](https://github.com/getgrav/grav-plugin-custom-css/issues/2) + +# v0.2.0 +## 05/23/2016 + +1. [](#new) + * Added field to manipulate priority + +# v0.1.0 +## 05/02/2016 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/custom-css/LICENSE b/plugins/custom-css/LICENSE new file mode 100644 index 0000000..4bb7092 --- /dev/null +++ b/plugins/custom-css/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Grav + +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/plugins/custom-css/README.md b/plugins/custom-css/README.md new file mode 100644 index 0000000..2db9579 --- /dev/null +++ b/plugins/custom-css/README.md @@ -0,0 +1,9 @@ +# Custom CSS Plugin + +The **Custom CSS** Plugin for the [Grav CMS](http://github.com/getgrav/grav) allows to add additional CSS to your site, without editing the theme or having to create a plugin for the simple task of adding some custom lines of sylesheets. + +## Description + +Adds some custom CSS to your Grav site. + +Open the plugin preferences in the Admin panel, and add custom CSS directly in the textarea, which will be added as inline CSS, or enter one or more relative paths to load. (e.g. `/user/my_css/custom.css`) diff --git a/plugins/custom-css/blueprints.yaml b/plugins/custom-css/blueprints.yaml new file mode 100644 index 0000000..5fbbac9 --- /dev/null +++ b/plugins/custom-css/blueprints.yaml @@ -0,0 +1,48 @@ +name: Custom CSS +version: 0.2.2 +description: Adds some custom CSS to your Grav site +icon: plug +author: + name: Team Grav + email: devs@getgrav.org +homepage: https://github.com/getgrav/grav-plugin-custom-css +keywords: grav, plugin, css, design +bugs: https://github.com/getgrav/grav-plugin-custom-css/issues +readme: https://github.com/getgrav/grav-plugin-custom-css/blob/develop/README.md +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + css_inline: + type: editor + codemirror: + mode: css + label: PLUGIN_CUSTOM_CSS.INLINE_CSS + help: PLUGIN_CUSTOM_CSS.INLINE_CSS_HELP + + css_files: + type: list + label: PLUGIN_CUSTOM_CSS.CSS_FILES + help: PLUGIN_CUSTOM_CSS.CSS_FILES_HELP + fields: + .path: + type: text + label: PLUGIN_CUSTOM_CSS.CSS_FILES_PATH + help: PLUGIN_CUSTOM_CSS.CSS_FILES_PATH_HELP + .priority: + type: int + label: PLUGIN_CUSTOM_CSS.CSS_FILES_PATH_PRIORITY + help: PLUGIN_CUSTOM_CSS.CSS_FILES_PATH_PRIORITY_HELP + default: 0 diff --git a/plugins/custom-css/custom-css.php b/plugins/custom-css/custom-css.php new file mode 100644 index 0000000..7b42573 --- /dev/null +++ b/plugins/custom-css/custom-css.php @@ -0,0 +1,48 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize the plugin + */ + public function onPluginsInitialized() + { + // Don't proceed if we are in the admin plugin + if ($this->isAdmin()) { + return; + } + + $this->enable([ + 'onAssetsInitialized' => ['onAssetsInitialized', 0] + ]); + } + + public function onAssetsInitialized() + { + $this->grav['assets']->addInlineCss($this->config->get('plugins.custom-css.css_inline')); + + foreach($this->config->get('plugins.custom-css.css_files', []) as $file) { + if (trim($file['path']) !== '') { + $this->grav['assets']->addCss($file['path'], isset($file['priority']) ? $file['priority'] : null); + } + } + } +} diff --git a/plugins/custom-css/custom-css.yaml b/plugins/custom-css/custom-css.yaml new file mode 100644 index 0000000..d4ca941 --- /dev/null +++ b/plugins/custom-css/custom-css.yaml @@ -0,0 +1 @@ +enabled: true diff --git a/plugins/custom-css/languages.yaml b/plugins/custom-css/languages.yaml new file mode 100644 index 0000000..9f60a35 --- /dev/null +++ b/plugins/custom-css/languages.yaml @@ -0,0 +1,20 @@ +en: + PLUGIN_CUSTOM_CSS: + INLINE_CSS: "Inline CSS" + INLINE_CSS_HELP: "CSS that will be added inline to every page" + CSS_FILES: "CSS Files" + CSS_FILES_HELP: "CSS Files that will be loaded on every page. Use relative or absolute URLs" + CSS_FILES_PATH: "File path" + CSS_FILES_PATH_HELP: "Relative to web root" + CSS_FILES_PATH_PRIORITY: "Priority (0=Default)" + CSS_FILES_PATH_PRIORITY_HELP: "Lower means later inclusion. Negative value to add this file after the other files (that come with the theme)" +ru: + PLUGIN_CUSTOM_CSS: + INLINE_CSS: "Встроенный CSS" + INLINE_CSS_HELP: "CSS код, который будет добавлен на каждой странице" + CSS_FILES: "CSS файлы" + CSS_FILES_HELP: "CSS файлы, которые будут загружаться на каждой странице. Используйте относительные или абсолютные URL" + CSS_FILES_PATH: "Путь к файлу" + CSS_FILES_PATH_HELP: "Относительно корня" + CSS_FILES_PATH_PRIORITY: "Приоритет (0=По умолчанию)" + CSS_FILES_PATH_PRIORITY_HELP: "Меньше - файл подключается позже. Отрицательное значение для подключения этого файла после других файлов (которые поставляются с темой)" \ No newline at end of file diff --git a/plugins/email/CHANGELOG.md b/plugins/email/CHANGELOG.md new file mode 100644 index 0000000..455520e --- /dev/null +++ b/plugins/email/CHANGELOG.md @@ -0,0 +1,314 @@ +# v3.1.4 +## 11/16/2021 + +1. [](#improved) + * Added second parameter to `Email::send()` to get failed recipients + +# v3.1.3 +## 07/19/2021 + +1. [](#improved) + * Pass page variable to processed forms [#141](https://github.com/getgrav/grav-plugin-email/pull/141) + * Email configuration available to templates [#152](https://github.com/getgrav/grav-plugin-email/pull/152) + * New Event after eMail was sent [#151](https://github.com/getgrav/grav-plugin-email/pull/151) + +# v3.1.2 +## 04/06/2021 + +1. [](#new) + * Added new `onEmailMessage` event to make object available for editing [#150](https://github.com/getgrav/grav-plugin-email/pull/150) + +# v3.1.1 +## 01/31/2021 + +1. [](#improved) + * Latest vendor updates including SwiftMailer `6.2.5` + * Updated CLI commands + * Minor code cleanup + +# v3.1.0 +## 12/02/2020 + +1. [](#improved) + * Added support for `auth_mode` in SMTP engine [#101](https://github.com/getgrav/grav-plugin-email/pull/101) + * Obfuscate the password shown in the CLI `test-email` command [#140](https://github.com/getgrav/grav-plugin-email/pull/140) + +# v3.0.10 +## 11/09/2020 + +1. [](#improved) + * Tweaked default `base.html.twig` template to better support dark-mode clients + * Latest vendor updates +1. [](#bugfix) + * Add missing support for `template:` in body array + * Added check to process markdown with `text/html` content type only + +# v3.0.9 +## 06/08/2020 + +1. [](#improved) + * Disable password autocomplete in password field + * Don't save empty string in password field [#134](https://github.com/getgrav/grav-plugin-email/issues/134) + +# v3.0.8 +## 04/27/2020 + +1. [](#improved) + * Updated vendor library files + * Use Grav's Parsedown class + +# v3.0.7 +## 03/05/2020 + +1. [](#improved) + * Updated email validator library +1. [](#bugfix) + * Fixed `Invalid resource theme://` on CLI command `test-email` on Grav 1.6.21 and later versions [#128](https://github.com/getgrav/grav-plugin-email/issues/128) + +# v3.0.6 +## 02/11/2020 + +1. [](#improved) + * Updated email validator library + +# v3.0.5 +## 02/03/2020 + +1. [](#bugfix) + * Fixed a date in changelog (no other changes) + +# v3.0.4 +## 01/17/2020 + +1. [](#improved) + * Added ZOHO configuration example + * Updated SwiftMailer library for PHP 7.4 support + +# v3.0.3 +## 08/16/2019 + +1. [](#new) + * Support an array of multiple emails in `email:` form process + * Allow form values in email templates +1. [](#improved) + * Added Twig blocks for `content` and `footer` in `email/base.html.twig` template + * Updated `README.md` to reflect working setup for GMail + +# v3.0.2 +## 05/09/2019 + +1. [](#new) + * Requires Form Plugin v3.0.3 + * Added Russian translation [#113](https://github.com/getgrav/grav-plugin-email/pull/113) +1. [](#bugfix) + * Better fix for missing attachments when sending an email using a form [form#333](https://github.com/getgrav/grav-plugin-form/issues/333) + +# v3.0.1 +## 04/15/2019 + +1. [](#improved) + * Put a `try/catch` around email attachments and log any errors rather than hard fail +1. [](#bugfix) + * Fixed missing attachments when sending an email using a form [form#333](https://github.com/getgrav/grav-plugin-form/issues/333) + +# v3.0.0 +## 04/11/2019 + +1. [](#new) + * Added new `template:` to choose twig template option for email form processing + * Moved `buildMessage()` and `parseAddressValue()` to Email object and made public + * Refactored the `EmailUtils::sendEmail()` to take an array of params or the old param list + * Switched to SwiftMailer v.6.1.3 (requires PHP7/Grav 1.6) + * SwiftMailer 6.x compatibility fixes + * Updated various translations + * Added support for Email Queue with Scheduler support + * Code cleanup, composer update + * Added a new `clear-queue-failures` CLI command to flush out failed sends +1. [](#improved) + * Added backlink for scheduler task + * Added support for `environment` option to `flushqueue` CLI command + * Fixed mailtrap hostname in README.md + * Disable autocomplete on SMTP `user` and `password` fields + +# v2.7.2 +## 01/25/2019 + +1. [](#improved) + * Added default for `to` address + * Updated EN language [#99](https://github.com/getgrav/grav-plugin-email/pull/99) + * Updated UK language [#98](https://github.com/getgrav/grav-plugin-email/pull/98) + * Updated RU language [#100](https://github.com/getgrav/grav-plugin-email/pull/100) + * Updated to SwiftMailer v5.4.12 +1. [](#bugfix) + * Fixed `mailtrap` hostname + +# v2.7.1 +## 12/05/2017 + +1. [](#new) + * Added new `onEmailSend()` event hook before sending [#70](https://github.com/getgrav/grav-plugin-email/pull/70) +1. [](#improved) + * Added examples of setting up Email plugin with various SMTP providers + * Updated RU language [#60](https://github.com/getgrav/grav-plugin-email/pull/60) + * Updated to SwiftMailer v5.4.8 + +# v2.7.0 +## 10/26/2017 + +1. [](#improved) + * Now uses a dedicated `logs/email.log` file when `debug: true` + * Improved the README.txt file with examples, and troubleshooting + * Changed default engine to `sendmail` as `mail` is deprecated and not functioning [swiftmailer#866](https://github.com/swiftmailer/swiftmailer/issues/866} + +# v2.6.2 +## 09/30/2017 + +1. [](#improved) + * Removed extraneous files from vendor folder + +# v2.6.1 +## 09/07/2017 + +1. [](#improved) + * Improved the error message when missing `from` in the configuration + * Silently catch malformed email exceptions + +# v2.6.0 +## 05/22/2017 + +1. [](#improved) + * Inherit options from plugin configuration [#39](https://github.com/getgrav/grav-plugin-email/pull/39) +1. [](#bugfix) + * Also process translation on the email subject [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38) + +# v2.5.3 +## 01/03/2017 + +1. [](#improved) + * Updated to SwiftMailer 5.4.5 [#45](https://github.com/getgrav/grav-plugin-email/issues/45) + +# v2.5.2 +## 12/13/2016 + +1. [](#new) + * RC released as stable + +# v2.5.2-rc.1 +## 11/26/2016 + +1. [](#new) + * Added a new `process_markdown` option for emails in forms +1. [](#improved) + * Improved the `Utils::sendEmail()` method to take the email type as an option + +# v2.5.1 +## 10/19/2016 + +1. [](#improved) + * CLI command will fallback to use the `to` from email plugin config if not provided + * Explicit Composer based class loader to fix issues with class case + +# v2.5.0 +## 09/07/2016 + +1. [](#new) + * Added a new `bin/plugin email test-email` CLI command +1. [](#improved) + * Moved Email `Utils` class from Login to Email plugin + * Provide a sample base `email/base.html.twig` template for emails +1. [](#bugfix) + * Fix handling attachments with the updated file upload field + +# v2.4.3 +## 08/16/2016 + +1. [](#improved) + * Added Russian translation + * Updated Swiftmailer to 5.4.3 [#37](https://github.com/getgrav/grav-plugin-email/issues/37) + +# v2.4.2 +## 08/10/2016 + +1. [](#improved) + * Added Croatian translation + +# v2.4.1 +## 07/14/2016 + +1. [](#improved) + * Allow multiple email recipients (comma separated) [#31](https://github.com/getgrav/grav-plugin-email/issues/31) + * Added Danish and Spanish translations + +# v2.4.0 +## 05/11/2016 + +1. [](#improved) + * Now includes Swiftmailer v5.4.2 which introduces a number of bug fixes and improvements +1. [](#bugfix) + * Correct `starttls` implementation, bundled in TLS + +# v2.3.0 +## 04/20/2016 + +1. [](#improved) + * Added debug option to enable logging on SwiftMailer. + * Updated SwiftMailer from v5.1.0 to v5.4.1. + * Added an option in the Admin settings to enable `starttls` +1. [](#bugfix) + * Correctly name TLS in the Admin settings, the label was `TTS` (but the value was correctly named `tls`) + +# v2.2.0 +## 02/05/2016 + +1. [](#new) + * Allow to send attachments in forms + * Added French translation +1. [](#improved) + * Throw an exception when trying to send emails without a `from` or `to` parameters setup, to intercept less meaningful errors and provide a better description on how to fix the problem + * Changed SMTP password in admin to use a password field instead of plain text + +# v2.1.0 +## 12/18/2015 + +1. [](#new) + * Added missing `content_type` to email.yaml + * Added default values for CC and BCC + 1. [](#improved) + * Improved documentation of new email params in `README.md` + * Moved config setting of `mailer.default` to `mailer.engine` + +# v2.0.0 +## 12/11/2015 + +1. [](#new) + * Added support for from/sender name (Thomas Keitel) + * Added support for message content type (Thomas Keitel) + * Added support for reply addresses (Thomas Keitel) + * Added support for CC/BCC (Thomas Keitel) + * Added support for multiple body parts (Thomas Keitel) +1. [](#bugfix) + * Fix email engine selection (z38) + +# v1.0.0 +## 11/20/2015 + +1. [](#bugfix) + * Fix for issue with no body parameter specified + +# v0.2.1 +## 09/11/2015 + +1. [](#bugfix) + * Fix onFormProcessed event + +# v0.2.0 +## 08/11/2015 + +1. [](#improved) + * Disable `enable` in admin + +# v0.1.0 +## 08/04/2015 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/email/LICENSE b/plugins/email/LICENSE new file mode 100644 index 0000000..0e788c6 --- /dev/null +++ b/plugins/email/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Grav + +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/plugins/email/README.md b/plugins/email/README.md new file mode 100644 index 0000000..ec37e95 --- /dev/null +++ b/plugins/email/README.md @@ -0,0 +1,491 @@ +# Grav Email Plugin + +The **email plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to send email. This is particularly useful for the **admin** and **login** plugins. + +# Installation + +The email plugin is easy to install with GPM. + +``` +$ bin/gpm install email +``` + +# Configuration + +By default, the plugin uses PHP Mail as the mail engine. + +``` +enabled: true +from: +from_name: +to: +to_name: +queue: + enabled: true + flush_frequency: '* * * * *' + flush_msg_limit: 10 + flush_time_limit: 100 +mailer: + engine: sendmail + smtp: + server: localhost + port: 25 + encryption: none + user: '' + password: '' + auth_mode: '' + sendmail: + bin: '/usr/sbin/sendmail -bs' +content_type: text/html +debug: false +``` + +You can configure the Email plugin by using the Admin plugin, navigating to the Plugins list and choosing `Email`. + +That's the easiest route. Or you can also alter the Plugin configuration by copying the `user/plugins/email/email.yaml` file into `user/config/plugins/email.yaml` and make your modifications there. + +The first setting you'd likely change is your `Email from` / `Email to` names and emails. + +Also, you'd likely want to setup a SMTP server instead of using PHP Mail, as the latter is not 100% reliable and you might experience problems with emails. + +Valid values for `auth_mode` include `plain`, `login`, `cram-md5`, or `null`. + +> NOTE: `engine: mail` has been deprecated from the SwiftMail library that this plugin uses as it does not funtion at all. Please use `smtp` if at all possibe, and `sendmail` if SMTP is not an option. + +### Mailtrap.io + +A good way to test emails is to use a SMTP server service that's built for testing emails, for example [https://mailtrap.io](https://mailtrap.io) + +Setup the Email plugin to use that SMTP server with the fake inbox data. For example enter this configuration in `user/config/plugins/email.yaml` or through the Admin panel: + +``` +mailer: + engine: smtp + smtp: + server: smtp.mailtrap.io + port: 2525 + encryption: none + user: YOUR_MAILTRAP_INBOX_USER + password: YOUR_MAILTRAP_INBOX_PASSWORD +``` + +That service will intercept emails and show them on their web-based interface instead of sending them for real. + +You can try and fine tune the emails there while testing. + +### Google Email + +A popular option for sending email is to simply use your Google Accounts SMTP server. To set this up you will need to do 2 things first: + +1. Enable IMAP in your Gmail `Settings` -> `Forwarding and POP/IMAP` -> `IMAP Access` +2. Enable `Less secure apps` in your [user account settings](https://myaccount.google.com/lesssecureapps) +3. If you have 2-factor authentication, you will need to create a unique application password to use rather than your personal password + +Then configure the Email plugin: + +``` +mailer: + engine: smtp + smtp: + server: smtp.gmail.com + port: 587 + encryption: tls + user: 'YOUR_GOOGLE_EMAIL_ADDRESS' + password: 'YOUR_GOOGLE_PASSWORD' +``` + +> NOTE: Check your email sending limits: https://support.google.com/a/answer/166852?hl=en + +#### Sparkpost + +Generous email sending limits even in the free tier, and simple setup, make [Sparkpost](https://www.sparkpost.com) a great option for email sending. You just need to create an account, then setup a verified sending domain. Sparkpost does a nice job of making this process very easy and undertandable. Then just click on the SMTP Relay option to get your details for the configuration: + +``` +mailer: + engine: smtp + smtp: + server: smtp.sparkpostmail.com + port: 587 + encryption: tls + user: 'SMTP_Injection' + password: 'SEND_EMAIL_API_KEY' +``` + +Then try sending a test email... + +#### Sendgrid + +[Sendgrid](https://sendgrid.com) offers a very easy-to-setup serivce with 100 emails/day for free. The next level allows you to send 40k/email a day for just $10/month. Configuration is pretty simple, just create an account, then click SMTP integration and click the button to create an API key. The configuration is as follows: + +``` +mailer: + engine: smtp + smtp: + server: smtp.sendgrid.net + port: 587 + encryption: tls + user: 'apikey' + password: 'YOUR_SENDGRID_API_KEY' +``` + +#### Mailgun + +[Mailgun is a great service](https://www.mailgun.com/) that offers 10k/emails per month for free. Setup does require SPIF domain verification so that means you need to add at least a TXT entry in your DNS. This is pretty standard for SMTP sending services and does provide verification for remote email servers and makes your email sending more reliable. The Mailgun site, walks you through this process however, and the verification process is simple and fast. + +``` +mailer: + engine: smtp + smtp: + server: smtp.mailgun.org + port: 587 + encryption: tls + user: 'MAILGUN_EMAIL_ADDRESS' + password: 'MAILGUN_EMAIL_PASSWORD' +``` + +Adjust these configurations for your account. + +#### MailJet + +Mailjet is another great service that is easy to quickly setup and get started sending email. The free account gives you 200 emails/day or 600 emails/month. Just signup and setup your SPF and DKIM entries for your domain. Then click on the SMTP settings and use those to configure the email plugin: + +``` +mailer: + engine: smtp + smtp: + server: in-v3.mailjet.com + port: 587 + encryption: tls + user: 'MAILJUST_USERNAME_API_KEY' + password: 'MAILJUST_PASSWORD_SECRET_KEY' +``` + +It's that easy! + +#### ZOHO + +ZOHO is a popular solution for hosted email due to it's great 'FREE' tier. It's paid options are also very reasonable and combined with the latest UI updates and outstanding security features, it's a solid email option. + +In order to get ZOHO working with Grav, you need to send email via a user account. You can either use your users' password or generate an **App Password** via your ZOHO account (clicking on your avatar once logged in), then navigating to `Two Factor Authentication -> App Passwords -> Generate`. Just enter a unique app name (i.e. `Grav Website`). + +NOTE: The SMTP host required can be found in `Settings -> Mail - > Mail Accounts -> POP/IMAP -> SMTP`. This will provide the SMTP server for this account (it may not be `imap.zoho.com` depending on what region you are in) + +``` +mailer: + engine: smtp + smtp: + server: smtp.zoho.com + port: 587 + encryption: tls + user: 'ZOHO_EMAIL_ADDRESS' + password: 'ZOHO_EMAIL_PASSWORD' +``` + +#### Sendmail + +Although not as reliable as SMTP not providing as much debug information, sendmail is a simple option as long as your hosting provider is not blocking the default SMTP port `25`: + +``` +mailer: + engine: sendmail + sendmail: + bin: '/usr/sbin/sendmail -bs' +``` + +Simply adjust your binary command line to suite your environment + +### SMTP Email Services + +Solid SMTP options that even provide a FREE tier for low email volumes include: + +* SendGrid (100/day free) - https://sendgrid.com +* Mailgun - (10k/month free) - https://www.mailgun.com +* Mailjet - (6k/month free) - https://www.mailjet.com/ +* Sparkpost - (15k/month free) - https://www.sparkpost.com +* Amazon SES (62k/month free) - https://aws.amazon.com/ses/ + +If you are still unsure why should be using one in the first place, check out this article: https://zapier.com/learn/email-marketing/best-transactional-email-sending-services/ + +## Email Queue + +For performance reasons, it's often desirable to queue emails and send them in batches, rather than forcing Grav to wait while an email is sent. This is because email servers are sometimes slow and you might not want to wait for the whole email-sending process before continuing with Grav processing. + +To address this, you can enable the **Email Queue** and this will ensure all email's in Grav are actually sent to the queue, and not sent directly. In order for the emails to be actually sent, you need to flush the queue. By default this is handled by the **Grav Scheduler**, so you need to ensure you have that enabled and setup correctly or **your emails will not be sent!!!**. + +You can also manually flush the queue by using the provided CLI command: + +``` +$ bin/plugin email flush-queue +``` + +## Testing with CLI Command + +You can test your email configuration with the following CLI Command: + +``` +$ bin/plugin email test-email -t steve@apple.com +``` + +You can also pass in a configuration environment: + +``` +$ bin/plugin email test-email -t steve@apple.com -e mysite.com +``` + +This will use the email configuration you have located in `user/mysite.com/config/plugins/email.yaml`. Read the docs to find out more about environment-based configuration: https://learn.getgrav.org/advanced/environment-config + +# Programmatically send emails + +Add this code in your plugins: + +```php + + $to = 'email@test.com'; + $from = 'email@test.com'; + + $subject = 'Test'; + $content = 'Test'; + + $message = $this->grav['Email']->message($subject, $content, 'text/html') + ->setFrom($from) + ->setTo($to); + + $sent = $this->grav['Email']->send($message); +``` + +# Emails sent with Forms + +When executing email actions during form processing, action parameters are inherited from the global configuration but may also be overridden on a per-action basis. + +``` +title: Custom form + +form: + name: custom_form + fields: + + # Any fields you'd like to add to the form: + # Their values may be referenced in email actions via '{{ form.value.FIELDNAME|e }}' + + process: + email: + subject: "[Custom form] {{ form.value.name|e }}" + body: "{% include 'forms/data.txt.twig' %}" + from: sender@example.com + from_name: 'Custom sender name' + to: recipient@example.com + to_name: 'Custom recipient name' + content_type: 'text/plain' + process_markdown: true +``` + +## Multiple Emails + +You can send multiple emails by creating an array of emails under the `process: email:` option in the form: + +``` +title: Custom form + +form: + name: custom_form + fields: + + # Any fields you'd like to add to the form: + # Their values may be referenced in email actions via '{{ form.value.FIELDNAME|e }}' + + process: + email: + - + subject: "[Custom Email 1] {{ form.value.name|e }}" + body: "{% include 'forms/data.txt.twig' %}" + from: {mail: "owner@mysite.com", name: "Site OWner"} + to: {mail: "recepient_1@example.com", name: "Recepient 1"} + template: "email/base.html.twig" + - + subject: "[Custom Email 2] {{ form.value.name|e }}" + body: "{% include 'forms/data.txt.twig' %}" + from: {mail: "owner@mysite.com", name: "Site OWner"} + to: {mail: "recepient_2@example.com", name: "Recepient 1"} + template: "email/base.html.twig" +``` + +## Templating Emails + +You can specify a Twig template for HTML rendering, else Grav will use the default one `email/base.html.twig` which is included in this plugin. You can also specify a custom template that extends the base, where you can customize the `{% block content %}` and `{% block footer %}`. For example: + +```twig +{% extends 'email/base.html.twig' %} + +{% block content %} +

    + Greetings {{ form.value.name|e }}, +

    + +

    + We have received your request for help. Our team will get in touch with you within 3 Business Days. +

    + +

    + Regards, +

    + +

    + My Company +

    + E - help@mycompany.com
    + M - +1 555-123-4567
    + W - mycompany.com +

    +{% endblock %} + +{% block footer %} +

    My Company - All Rights Reserved

    +{% endblock %} +``` + +## Sending Attachments + +You can add file inputs to your form, and send those files via Email. +Just add an `attachments` field and list the file input fields names. You can have multiple file fields, and this will send all the files as attachments. Example: + +``` +form: + name: custom_form + fields: + + my-file: + label: 'Add a file' + type: file + multiple: false + destination: user/data/files + accept: + - application/pdf + - application/x-pdf + - image/png + - text/plain + + process: + + email: + body: '{% include "forms/data.html.twig" %}' + attachments: + - 'my-file' +``` + +## Additional action parameters + +To have more control over your generated email, you may also use the following additional parameters: + +* `reply_to`: Set one or more addresses that should be used to reply to the message. +* `cc` _(Carbon copy)_: Add one or more addresses to the delivery list. Many email clients will mark email in one's inbox differently depending on whether they are in the `To:` or `Cc:` list. +* `bcc` _(Blind carbon copy)_: Add one or more addresses to the delivery list that should (usually) not be listed in the message data, remaining invisible to other recipients. +* `charset`: Explicitly set a charset for the generated email body (only takes effect if `body` parameter is a string, defaults to `utf-8`) + +### Specifying email addresses + +Email-related parameters (`from`, `to`, `reply_to`, `cc`and `bcc`) allow different notations for single / multiple values: + +#### Single email address string + +``` +to: mail@example.com +``` + +#### Multiple email address strings + +``` +to: + - mail@example.com + - mail+1@example.com + - mail+2@example.com +``` + +#### Single email address with name + +``` +to: + mail: mail@example.com + name: Human-readable name +``` + +or inline: + +``` +to: {mail: 'mail@example.com', name: 'Human-readable name'} +``` + +#### Multiple email addresses (with and without names) + +``` +to: + - + mail: mail@example.com + name: Human-readable name + - + mail: mail+2@example.com + name: Another human-readable name + - + mail+3@example.com + - + mail+4@example.com +``` + +or inline: + +``` +to: + - {mail: 'mail@example.com', name: 'Human-readable name'} + - {mail: 'mail+2@example.com', name: 'Another human-readable name'} + - mail+3@example.com + - mail+4@example.com +``` + +## Multi-part MIME messages + +Apart from a simple string, an email body may contain different MIME parts (e.g. HTML body with plain text fallback). You may even specify a different charset for each part (default to `utf-8`): + +``` +body: + - + content_type: 'text/html' + body: "{% include 'forms/default/data.html.twig' %}" + - + content_type: 'text/plain' + body: "{% include 'forms/default/data.txt.twig' %}" + charset: 'iso-8859-1' +``` + +# Troubleshooting + +## Emails are not sent + +#### Debugging + +The first step in determining why emails are not sent is to enable debugging. This can be done via the `user/config/email.yaml` file or via the plugin settings in the admin. Just enable this and then try sending an email again. Then inspect the `logs/email.log` file for potential problems. + +#### ISP Port 25 blocking + +By default, when sending via PHP or Sendmail the machine running the webserver will attempt to send mail using the SMTP protocol. This uses port `25` which is often blocked by ISPs to protected against spamming. You can determine if this port is blocked by running this command in your temrinal (mac/linux only): + +``` +(echo >/dev/tcp/localhost/25) &>/dev/null && echo "TCP port 25 opened" || echo "TCP port 25 closed" +``` + +If it's blocked there are ways to configure relays to different ports, but the simplest solution is to use SMTP for mail sending. + + +#### Exceptions + +If you get an exception when sending email but you cannot see what the error is, you need to enable more verbose exception messages. In the `user/config/system.yaml` file ensure your have the following configuration: + +``` +errors: + display: 1 + log: true +``` + +## Configuration Issues + +As explained above in the Configuration section, if you're using the default settings, set the Plugin configuration to use a SMTP server. It can be [Gmail](https://www.digitalocean.com/community/tutorials/how-to-use-google-s-smtp-server) or another SMTP server you have at your disposal. + +This is the first thing to check. The reason is that PHP Mail, the default system used by the Plugin, is not 100% reliable and emails might not arrive. diff --git a/plugins/email/blueprints.yaml b/plugins/email/blueprints.yaml new file mode 100644 index 0000000..483e3d5 --- /dev/null +++ b/plugins/email/blueprints.yaml @@ -0,0 +1,251 @@ +name: Email +slug: email +type: plugin +version: 3.1.4 +testing: false +description: Enables the emailing system for Grav +icon: envelope +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +keywords: plugin, email, sender +homepage: https://github.com/getgrav/grav-plugin-email +bugs: https://github.com/getgrav/grav-plugin-email/issues +license: MIT + +dependencies: + - { name: grav, version: '>=1.6.0' } + - { name: form, version: '>=3.0.3' } + +form: + validation: loose + + fields: + enabled: + type: hidden + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + mailer.engine: + type: select + label: PLUGIN_EMAIL.MAIL_ENGINE + size: medium + options: + none: PLUGIN_ADMIN.DISABLED + smtp: SMTP + sendmail: Sendmail + + content_type: + type: select + label: PLUGIN_EMAIL.CONTENT_TYPE + size: medium + default: 'text/html' + options: + 'text/plain': PLUGIN_EMAIL.CONTENT_TYPE_PLAIN_TEXT + 'text/html': HTML + + charset: + type: text + size: medium + label: PLUGIN_EMAIL.CHARSET + placeholder: PLUGIN_EMAIL.CHARSET_PLACEHOLDER + + email_Defaults: + type: section + title: PLUGIN_EMAIL.EMAIL_DEFAULTS + underline: true + + from: + type: email + size: medium + label: PLUGIN_EMAIL.EMAIL_FORM + placeholder: PLUGIN_EMAIL.EMAIL_FORM_PLACEHOLDER + validate: + required: true + type: email + + from_name: + type: text + size: medium + label: PLUGIN_EMAIL.EMAIL_FROM_NAME + placeholder: PLUGIN_EMAIL.EMAIL_FROM_NAME_PLACEHOLDER + + to: + type: email + size: medium + label: PLUGIN_EMAIL.EMAIL_TO + placeholder: PLUGIN_EMAIL.EMAIL_TO_PLACEHOLDER + multiple: true + validate: + required: true + type: email + + to_name: + type: text + size: medium + label: PLUGIN_EMAIL.EMAIL_TO_NAME + placeholder: PLUGIN_EMAIL.EMAIL_TO_NAME_PLACEHOLDER + + cc: + type: email + size: medium + label: PLUGIN_EMAIL.EMAIL_CC + placeholder: PLUGIN_EMAIL.EMAIL_CC_PLACEHOLDER + multiple: true + validate: + type: email + + cc_name: + type: text + size: medium + label: PLUGIN_EMAIL.EMAIL_CC_NAME + placeholder: PLUGIN_EMAIL.EMAIL_CC_NAME_PLACEHOLDER + + bcc: + type: email + size: medium + label: PLUGIN_EMAIL.EMAIL_BCC + placeholder: PLUGIN_EMAIL.EMAIL_BCC_PLACEHOLDER + multiple: true + validate: + type: email + + reply_to: + type: email + size: medium + label: PLUGIN_EMAIL.EMAIL_REPLY_TO + placeholder: PLUGIN_EMAIL.EMAIL_REPLY_TO_PLACEHOLDER + multiple: true + validate: + type: email + + reply_to_name: + type: text + size: medium + label: PLUGIN_EMAIL.EMAIL_REPLY_TO_NAME + placeholder: PLUGIN_EMAIL.EMAIL_REPLY_TO_NAME_PLACEHOLDER + + body: + type: textarea + size: medium + label: PLUGIN_EMAIL.EMAIL_BODY + placeholder: PLUGIN_EMAIL.EMAIL_BODY_PLACEHOLDER + + smtp_config: + type: section + title: PLUGIN_EMAIL.SMTP_CONFIGURATION + underline: true + + mailer.smtp.server: + type: text + size: medium + label: PLUGIN_EMAIL.SMTP_SERVER + placeholder: PLUGIN_EMAIL.SMTP_SERVER_PLACEHOLDER + + mailer.smtp.port: + type: text + size: small + label: PLUGIN_EMAIL.SMTP_PORT + placeholder: PLUGIN_EMAIL.SMTP_PORT_PLACEHOLDER + validate: + type: number + min: 1 + max: 65535 + + mailer.smtp.encryption: + type: select + size: medium + label: PLUGIN_EMAIL.SMTP_ENCRYPTION + options: + none: PLUGIN_EMAIL.SMTP_ENCRYPTION_NONE + ssl: SSL + tls: TLS + + mailer.smtp.user: + type: text + size: medium + autocomplete: 'off' + label: PLUGIN_EMAIL.SMTP_LOGIN_NAME + + mailer.smtp.password: + type: password + size: medium + autocomplete: 'new-password' + label: PLUGIN_EMAIL.SMTP_PASSWORD + + mailer.smtp.auth_mode: + type: text + size: medium + label: PLUGIN_EMAIL.SMTP_AUTH_MODE + + sendmail_config: + type: section + title: PLUGIN_EMAIL.SENDMAIL_CONFIGURATION + underline: true + + mailer.sendmail.bin: + type: text + size: medium + label: PLUGIN_EMAIL.PATH_TO_SENDMAIL + placeholder: "/usr/sbin/sendmail" + + queue_section: + type: section + title: PLUGIN_EMAIL.QUEUE_TITLE + text: PLUGIN_EMAIL.QUEUE_DESC + markdown: true + underline: true + + queue.enabled: + type: toggle + label: PLUGIN_EMAIL.QUEUE_ENABLED + highlight: 0 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + queue.flush_frequency: + type: cron + label: PLUGIN_EMAIL.QUEUE_FLUSH_FREQUENCY + size: medium + help: PLUGIN_EMAIL.QUEUE_FLUSH_FREQUENCY_HELP + default: '* * * * *' + placeholder: '* * * * *' + + queue.flush_msg_limit: + type: number + label: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT + size: x-small + append: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT_APPEND + + queue.flush_time_limit: + type: number + label: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT + size: x-small + append: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT_APPEND + + advanced_section: + type: section + title: PLUGIN_EMAIL.ADVANCED + underline: true + + debug: + type: toggle + label: PLUGIN_EMAIL.DEBUG + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/plugins/email/classes/Email.php b/plugins/email/classes/Email.php new file mode 100644 index 0000000..689a7cd --- /dev/null +++ b/plugins/email/classes/Email.php @@ -0,0 +1,560 @@ +get('plugins.email.mailer.engine') !== 'none'; + } + + /** + * Returns true if debugging on emails has been enabled. + * + * @return bool + */ + public static function debug() + { + return Grav::instance()['config']->get('plugins.email.debug') == 'true'; + } + + /** + * Creates an email message. + * + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * @return \Swift_Message + */ + public function message($subject = null, $body = null, $contentType = null, $charset = null) + { + return new \Swift_Message($subject, $body, $contentType, $charset); + } + + /** + * Creates an attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_Attachment + */ + public function attachment($data = null, $filename = null, $contentType = null) + { + return new \Swift_Attachment($data, $filename, $contentType); + } + + /** + * Creates an embedded attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_EmbeddedFile + */ + public function embedded($data = null, $filename = null, $contentType = null) + { + return new \Swift_EmbeddedFile($data, $filename, $contentType); + } + + /** + * Creates an image attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_Image + */ + public function image($data = null, $filename = null, $contentType = null) + { + return new \Swift_Image($data, $filename, $contentType); + } + + /** + * Send email. + * + * @param \Swift_Message $message + * @param array|null $failedRecipients + * @return int + */ + public function send($message, &$failedRecipients = null) + { + $mailer = $this->getMailer(); + + $result = $mailer ? $mailer->send($message, $failedRecipients) : 0; + + // Check if emails and debugging are both enabled. + if ($mailer && $this->debug()) { + + $log = new Logger('email'); + $locator = Grav::instance()['locator']; + $log_file = $locator->findResource('log://email.log', true, true); + $log->pushHandler(new StreamHandler($log_file, Logger::DEBUG)); + + // Append the SwiftMailer log to the log. + $log->addDebug($this->getLogs()); + } + + return $result; + } + + /** + * Build e-mail message. + * + * @param array $params + * @param array $vars + * @return \Swift_Message + */ + public function buildMessage(array $params, array $vars = []) + { + /** @var Twig $twig */ + $twig = Grav::instance()['twig']; + + /** @var Config $config */ + $config = Grav::instance()['config']; + + /** @var Language $language */ + $language = Grav::instance()['language']; + + // Extend parameters with defaults. + $params += [ + 'bcc' => $config->get('plugins.email.bcc', []), + 'body' => $config->get('plugins.email.body', '{% include "forms/data.html.twig" %}'), + 'cc' => $config->get('plugins.email.cc', []), + 'cc_name' => $config->get('plugins.email.cc_name'), + 'charset' => $config->get('plugins.email.charset', 'utf-8'), + 'from' => $config->get('plugins.email.from'), + 'from_name' => $config->get('plugins.email.from_name'), + 'content_type' => $config->get('plugins.email.content_type', 'text/html'), + 'reply_to' => $config->get('plugins.email.reply_to', []), + 'reply_to_name' => $config->get('plugins.email.reply_to_name'), + 'subject' => !empty($vars['form']) && $vars['form'] instanceof FormInterface ? $vars['form']->page()->title() : null, + 'to' => $config->get('plugins.email.to'), + 'to_name' => $config->get('plugins.email.to_name'), + 'process_markdown' => false, + 'template' => false + ]; + + // Create message object. + $message = $this->message(); + + if (!$params['to']) { + throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_TO_ADDRESS')); + } + if (!$params['from']) { + throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS')); + } + + // make email configuration available to templates + $vars += [ + 'email' => $params, + ]; + + // Process parameters. + foreach ($params as $key => $value) { + switch ($key) { + case 'body': + if (is_string($value)) { + if (strpos($value, '{{') !== false || strpos($value, '{%') !== false) { + $body = $twig->processString($value, $vars); + } else { + $body = $value; + } + + if ($params['process_markdown'] && $params['content_type'] === 'text/html') { + $parsedown = new Parsedown(); + $body = $parsedown->text($body); + } + + if ($params['template']) { + $body = $twig->processTemplate($params['template'], ['content' => $body] + $vars); + } + + $content_type = !empty($params['content_type']) ? $twig->processString($params['content_type'], $vars) : null; + $charset = !empty($params['charset']) ? $twig->processString($params['charset'], $vars) : null; + + $message->setBody($body, $content_type, $charset); + } elseif (is_array($value)) { + foreach ($value as $body_part) { + $body_part += [ + 'charset' => $params['charset'], + 'content_type' => $params['content_type'], + ]; + + $body = !empty($body_part['body']) ? $twig->processString($body_part['body'], $vars) : null; + + if ($params['process_markdown'] && $body_part['content_type'] === 'text/html') { + $parsedown = new Parsedown(); + $body = $parsedown->text($body); + } + + if (isset($body_part['template'])) { + $body = $twig->processTemplate($body_part['template'], ['content' => $body] + $vars); + } + + $content_type = !empty($body_part['content_type']) ? $twig->processString($body_part['content_type'], $vars) : null; + $charset = !empty($body_part['charset']) ? $twig->processString($body_part['charset'], $vars) : null; + + if (!$message->getBody()) { + $message->setBody($body, $content_type, $charset); + } + else { + $message->addPart($body, $content_type, $charset); + } + } + } + break; + + case 'subject': + $message->setSubject($twig->processString($language->translate($value), $vars)); + break; + + case 'to': + if (is_string($value) && !empty($params['to_name'])) { + $value = [ + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['to_name'], $vars), + ]; + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + $message->addTo($address->mail, $address->name); + } + break; + + case 'cc': + if (is_string($value) && !empty($params['cc_name'])) { + $value = [ + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['cc_name'], $vars), + ]; + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + $message->addCc($address->mail, $address->name); + } + break; + + case 'bcc': + foreach ($this->parseAddressValue($value, $vars) as $address) { + $message->addBcc($address->mail, $address->name); + } + break; + + case 'from': + if (is_string($value) && !empty($params['from_name'])) { + $value = [ + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['from_name'], $vars), + ]; + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + $message->addFrom($address->mail, $address->name); + } + break; + + case 'reply_to': + if (is_string($value) && !empty($params['reply_to_name'])) { + $value = [ + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['reply_to_name'], $vars), + ]; + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + $message->addReplyTo($address->mail, $address->name); + } + break; + + } + } + + return $message; + } + + /** + * Return parsed e-mail address value. + * + * @param string|string[] $value + * @param array $vars + * @return array + */ + public function parseAddressValue($value, array $vars = []) + { + $parsed = []; + + /** @var Twig $twig */ + $twig = Grav::instance()['twig']; + + // Single e-mail address string + if (is_string($value)) { + $parsed[] = (object) [ + 'mail' => $twig->processString($value, $vars), + 'name' => null, + ]; + } + + else { + // Cast value as array + $value = (array) $value; + + // Single e-mail address array + if (!empty($value['mail'])) { + $parsed[] = (object) [ + 'mail' => $twig->processString($value['mail'], $vars), + 'name' => !empty($value['name']) ? $twig->processString($value['name'], $vars) : NULL, + ]; + } + + // Multiple addresses (either as strings or arrays) + elseif (!(empty($value['mail']) && !empty($value['name']))) { + foreach ($value as $y => $itemx) { + $addresses = $this->parseAddressValue($itemx, $vars); + + if (($address = reset($addresses))) { + $parsed[] = $address; + } + } + } + } + + return $parsed; + } + + /** + * Return debugging logs if enabled + * + * @return string + */ + public function getLogs() + { + if ($this->debug()) { + return $this->logger->dump(); + } + return ''; + } + + /** + * @internal + * @return null|\Swift_Mailer + */ + protected function getMailer() + { + if (!$this->enabled()) { + return null; + } + + if (!$this->mailer) { + /** @var Config $config */ + $config = Grav::instance()['config']; + $queue_enabled = $config->get('plugins.email.queue.enabled'); + + $transport = $queue_enabled === true ? $this->getQueue() : $this->getTransport(); + + // Create the Mailer using your created Transport + $this->mailer = new \Swift_Mailer($transport); + + // Register the logger if we're debugging. + if ($this->debug()) { + $this->logger = new \Swift_Plugins_Loggers_ArrayLogger(); + $this->mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($this->logger)); + } + } + + return $this->mailer; + } + + protected static function getQueuePath() + { + $queue_path = Grav::instance()['locator']->findResource('user://data', true) . '/email-queue'; + + if (!file_exists($queue_path)) { + mkdir($queue_path); + } + + return $queue_path; + } + + protected static function getQueue() + { + $queue_path = static::getQueuePath(); + + $spool = new \Swift_FileSpool($queue_path); + $transport = new \Swift_SpoolTransport($spool); + + return $transport; + } + + public static function flushQueue() + { + $grav = Grav::instance(); + + $grav['debugger']->enabled(false); + + $config = $grav['config']->get('plugins.email.queue'); + + try { + $queue = static::getQueue(); + $spool = $queue->getSpool(); + $spool->setMessageLimit($config['flush_msg_limit']); + $spool->setTimeLimit($config['flush_time_limit']); + $failures = []; + $result = $spool->flushQueue(static::getTransport(), $failures); + return $result . ' messages flushed from queue...'; + } catch (\Exception $e) { + $grav['log']->error($e->getMessage()); + return $e->getMessage(); + } + + } + + public static function clearQueueFailures() + { + $grav = Grav::instance(); + $grav['debugger']->enabled(false); + + $preferences = \Swift_Preferences::getInstance(); + $preferences->setTempDir(sys_get_temp_dir()); + + /** @var \Swift_Transport $transport */ + $transport = static::getTransport(); + if (!$transport->isStarted()) { + $transport->start(); + } + + $queue_path = static::getQueuePath(); + + foreach (new \GlobIterator($queue_path . '/*.sending') as $file) { + $final_message = $file->getPathname(); + + /** @var \Swift_Message $message */ + $message = unserialize(file_get_contents($final_message)); + + echo(sprintf( + 'Retrying "%s" to "%s"', + $message->getSubject(), + implode(', ', array_keys($message->getTo())) + ) . "\n"); + + try { + $clean = static::cloneMessage($message); + $transport->send($clean); + echo("sent!\n"); + + // DOn't want to trip up any errors from sending too fast + sleep(1); + } catch (\Swift_TransportException $e) { + echo("ERROR: Send failed - deleting spooled message\n"); + } + + // Remove the file + unlink($final_message); + } + } + + /** + * Clean copy a message + * + * @param \Swift_Message $message + */ + public static function cloneMessage($message) + { + $clean = new \Swift_Message(); + + $clean->setBoundary($message->getBoundary()); + $clean->setBcc($message->getBcc()); + $clean->setBody($message->getBody()); + $clean->setCharset($message->getCharset()); + $clean->setChildren($message->getChildren()); + $clean->setContentType($message->getContentType()); + $clean->setCc($message->getCc()); + $clean->setDate($message->getDate()); + $clean->setDescription($message->getDescription()); + $clean->setEncoder($message->getEncoder()); + $clean->setFormat($message->getFormat()); + $clean->setFrom($message->getFrom()); + $clean->setId($message->getId()); + $clean->setMaxLineLength($message->getMaxLineLength()); + $clean->setPriority($message->getPriority()); + $clean->setReplyTo($message->getReplyTo()); + $clean->setReturnPath($message->getReturnPath()); + $clean->setSender($message->getSender()); + $clean->setSubject($message->getSubject()); + $clean->setTo($message->getTo()); + $clean->setAuthMode($message->getAuthMode()); + + return $clean; + + } + + protected static function getTransport() + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + $engine = $config->get('plugins.email.mailer.engine'); + + // Create the Transport and initialize it. + switch ($engine) { + case 'smtp': + $transport = new \Swift_SmtpTransport(); + + $options = $config->get('plugins.email.mailer.smtp'); + if (!empty($options['server'])) { + $transport->setHost($options['server']); + } + if (!empty($options['port'])) { + $transport->setPort($options['port']); + } + if (!empty($options['encryption']) && $options['encryption'] !== 'none') { + $transport->setEncryption($options['encryption']); + } + if (!empty($options['user'])) { + $transport->setUsername($options['user']); + } + if (!empty($options['password'])) { + $transport->setPassword($options['password']); + } + if (!empty($options['auth_mode'])) { + $transport->setAuthMode($options['auth_mode']); + } + break; + case 'sendmail': + default: + $options = $config->get('plugins.email.mailer.sendmail'); + $bin = !empty($options['bin']) ? $options['bin'] : '/usr/sbin/sendmail'; + $transport = new \Swift_SendmailTransport($bin); + break; + } + + return $transport; + } +} diff --git a/plugins/email/classes/Utils.php b/plugins/email/classes/Utils.php new file mode 100644 index 0000000..69b85b4 --- /dev/null +++ b/plugins/email/classes/Utils.php @@ -0,0 +1,48 @@ + $params + * + * @return bool True if the action was performed. + */ + public static function sendEmail(...$params) + { + if (is_array($params[0])) { + $params = array_shift($params); + } else { + $keys = ['subject', 'body', 'to', 'from', 'content_type']; + $params = GravUtils::arrayCombine($keys, $params); + } + + //Initialize twig if not yet initialized + /** @var Twig $twig */ + $twig = Grav::instance()['twig']->init(); + + /** @var Email $email */ + $email = Grav::instance()['Email']; + + if (empty($params['to']) || empty($params['subject']) || empty($params['body'])) { + return false; + } + + $params['body'] = $twig->processTemplate('email/base.html.twig', ['content' => $params['body']]); + + $message = $email->buildMessage($params); + + return $email->send($message); + } +} diff --git a/plugins/email/cli/ClearQueueFailuresCommand.php b/plugins/email/cli/ClearQueueFailuresCommand.php new file mode 100644 index 0000000..705c825 --- /dev/null +++ b/plugins/email/cli/ClearQueueFailuresCommand.php @@ -0,0 +1,62 @@ +setName('clear-queue-failures') + ->setAliases(['clearqueue']) + ->addOption( + 'env', + 'e', + InputOption::VALUE_OPTIONAL, + 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com' + ) + ->setDescription('Clears any queue failures that have accumulated') + ->setHelp('The clear-queue-failures command clears any queue failures that have accumulated'); + } + + /** + * @return int + */ + protected function serve() + { + // TODO: remove when requiring Grav 1.7+ + if (method_exists($this, 'initializeGrav')) { + $this->initializeGrav(); + } + + + $this->output->writeln(''); + $this->output->writeln('Current Configuration:'); + $this->output->writeln(''); + + $grav = Grav::instance(); + dump($grav['config']->get('plugins.email')); + + $this->output->writeln(''); + + require_once __DIR__ . '/../vendor/autoload.php'; + + Email::clearQueueFailures(); + + return 0; + } +} diff --git a/plugins/email/cli/FlushQueueCommand.php b/plugins/email/cli/FlushQueueCommand.php new file mode 100644 index 0000000..3a735a8 --- /dev/null +++ b/plugins/email/cli/FlushQueueCommand.php @@ -0,0 +1,63 @@ +setName('flush-queue') + ->setAliases(['flushqueue']) + ->addOption( + 'env', + 'e', + InputOption::VALUE_OPTIONAL, + 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com' + ) + ->setDescription('Flushes the email queue of any pending emails') + ->setHelp('The flush-queue command flushes the email queue of any pending emails'); + } + + /** + * @return int + */ + protected function serve() + { + // TODO: remove when requiring Grav 1.7+ + if (method_exists($this, 'initializeGrav')) { + $this->initializeGrav(); + } + + $this->output->writeln(''); + $this->output->writeln('Current Configuration:'); + $this->output->writeln(''); + + $grav = Grav::instance(); + dump($grav['config']->get('plugins.email')); + + $this->output->writeln(''); + + require_once __DIR__ . '/../vendor/autoload.php'; + + $output = Email::flushQueue(); + + $this->output->writeln('' . $output . ''); + + return 0; + } +} diff --git a/plugins/email/cli/TestEmailCommand.php b/plugins/email/cli/TestEmailCommand.php new file mode 100644 index 0000000..0c07dd0 --- /dev/null +++ b/plugins/email/cli/TestEmailCommand.php @@ -0,0 +1,107 @@ +setName('test-email') + ->setAliases(['testemail']) + ->addOption( + 'to', + 't', + InputOption::VALUE_REQUIRED, + 'An email address to send the email to' + ) + ->addOption( + 'env', + 'e', + InputOption::VALUE_OPTIONAL, + 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com' + ) + ->addOption( + 'subject', + 's', + InputOption::VALUE_OPTIONAL, + 'A subject for the email' + ) + ->addOption( + 'body', + 'b', + InputOption::VALUE_OPTIONAL, + 'The body of the email' + ) + ->setDescription('Sends a test email using the plugin\'s configuration') + ->setHelp('The test-email command sends a test email using the plugin\'s configuration'); + } + + /** + * @return int + */ + protected function serve() + { + // TODO: remove when requiring Grav 1.7+ + if (method_exists($this, 'initializeGrav')) { + $this->initializeThemes(); + } + + $this->output->writeln(''); + $this->output->writeln('Current Configuration:'); + $this->output->writeln(''); + + $grav = Grav::instance(); + $email_config = new Data($grav['config']->get('plugins.email')); + if ($email_config->get('mailer.smtp.password')) { + $password = $email_config->get('mailer.smtp.password'); + $obfuscated_password = str_repeat('*', strlen($password) - 2) . substr($password, -2); + $email_config->set('mailer.smtp.password', $obfuscated_password); + } + + dump($email_config); + + $this->output->writeln(''); + + $grav['Email'] = new Email(); + + $to = $this->input->getOption('to') ?: $grav['config']->get('plugins.email.to'); + $subject = $this->input->getOption('subject'); + $body = $this->input->getOption('body'); + + if (!$subject) { + $subject = 'Testing Grav Email Plugin'; + } + + if (!$body) { + $configuration = print_r($email_config, true); + $body = $grav['language']->translate(['PLUGIN_EMAIL.TEST_EMAIL_BODY', $configuration]); + } + + $sent = EmailUtils::sendEmail(['subject'=>$subject, 'body'=>$body, 'to'=>$to]); + + if ($sent) { + $this->output->writeln("Message sent successfully!"); + } else { + $this->output->writeln("Problem sending email..."); + } + + return 0; + } +} diff --git a/plugins/email/composer.json b/plugins/email/composer.json new file mode 100644 index 0000000..5699706 --- /dev/null +++ b/plugins/email/composer.json @@ -0,0 +1,33 @@ +{ + "name": "getgrav/grav-plugin-email", + "type": "grav-plugin", + "description": "Email plugin for Grav CMS", + "keywords": ["email", "plugin", "sender"], + "homepage": "https://github.com/getgrav/grav-plugin-email", + "license": "MIT", + "authors": [ + { + "name": "Team Grav", + "email": "devs@getgrav.org", + "homepage": "https://getgrav.org", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/getgrav/grav-plugin-email/issues", + "irc": "https://chat.getgrav.org", + "forum": "https://getgrav.org/forum", + "docs": "https://github.com/getgrav/grav-plugin-email/blob/master/README.md" + }, + "require": { + "php": ">=7.1.3", + "swiftmailer/swiftmailer": "~6.0" + }, + "autoload": { + "psr-4": { + "Grav\\Plugin\\Email\\": "classes/", + "Grav\\Plugin\\Console\\": "cli/" + }, + "classmap": ["email.php"] + } +} diff --git a/plugins/email/composer.lock b/plugins/email/composer.lock new file mode 100644 index 0000000..cb08c72 --- /dev/null +++ b/plugins/email/composer.lock @@ -0,0 +1,651 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a841aff1de7e0364fe3e92ecd7b2fb56", + "packages": [ + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.25", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2020-12-29T14:50:06+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.2.5", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.0", + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "symfony/phpunit-bridge": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.5" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "type": "tidelift" + } + ], + "time": "2021-01-12T09:35:59+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6", + "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T17:09:11+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.3" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/plugins/email/email.php b/plugins/email/email.php new file mode 100644 index 0000000..93e2b1c --- /dev/null +++ b/plugins/email/email.php @@ -0,0 +1,181 @@ + ['onPluginsInitialized', 0], + 'onFormProcessed' => ['onFormProcessed', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + 'onSchedulerInitialized' => ['onSchedulerInitialized', 0], + 'onAdminSave' => ['onAdminSave', 0], + ]; + } + + /** + * Initialize emailing. + */ + public function onPluginsInitialized() + { + require_once __DIR__ . '/vendor/autoload.php'; + + $this->email = new Email(); + + if ($this->email->enabled()) { + $this->grav['Email'] = $this->email; + } + } + + /** + * Add twig paths to plugin templates. + */ + public function onTwigTemplatePaths() + { + $twig = $this->grav['twig']; + $twig->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Force compile during save if admin plugin save + * + * @param Event $event + */ + public function onAdminSave(Event $event) + { + /** @var Data $obj */ + $obj = $event['object']; + + + + if ($obj instanceof Data && $obj->blueprints()->getFilename() === 'email/blueprints') { + $current_pw = $this->grav['config']->get('plugins.email.mailer.smtp.password'); + $new_pw = $obj->get('mailer.smtp.password'); + if (!empty($current_pw) && empty($new_pw)) { + $obj->set('mailer.smtp.password', $current_pw); + } + + } + } + + /** + * Send email when processing the form data. + * + * @param Event $event + */ + public function onFormProcessed(Event $event) + { + $form = $event['form']; + $action = $event['action']; + $params = $event['params']; + + if (!$this->email->enabled()) { + return; + } + + switch ($action) { + case 'email': + // Prepare Twig variables + $vars = array( + 'form' => $form, + 'page' => $this->grav['page'] + ); + + // Copy files now, we need those. + // TODO: needs an update + $form->legacyUploads(); + $form->copyFiles(); + + $this->grav->fireEvent('onEmailSend', new Event(['params' => &$params, 'vars' => &$vars])); + + if ($this->isAssocArray($params)) { + $this->sendFormEmail($form, $params, $vars); + } else { + foreach ($params as $email) { + $this->sendFormEmail($form, $email, $vars); + } + } + + break; + } + } + + /** + * Add index job to Grav Scheduler + * Requires Grav 1.6.0 - Scheduler + */ + public function onSchedulerInitialized(Event $e) + { + if ($this->config->get('plugins.email.queue.enabled')) { + + /** @var Scheduler $scheduler */ + $scheduler = $e['scheduler']; + $at = $this->config->get('plugins.email.queue.flush_frequency'); + $logs = 'logs/email-queue.out'; + $job = $scheduler->addFunction('Grav\Plugin\Email\Email::flushQueue', [], 'email-flushqueue'); + $job->at($at); + $job->output($logs); + $job->backlink('/plugins/email'); + } + } + + protected function sendFormEmail($form, $params, $vars) + { + // Build message + $message = $this->email->buildMessage($params, $vars); + + if (isset($params['attachments'])) { + $filesToAttach = (array)$params['attachments']; + if ($filesToAttach) foreach ($filesToAttach as $fileToAttach) { + $filesValues = $form->value($fileToAttach); + + if ($filesValues) foreach($filesValues as $fileValues) { + if (isset($fileValues['file'])) { + $filename = $fileValues['file']; + } else { + $filename = ROOT_DIR . $fileValues['path']; + } + + try { + $message->attach(\Swift_Attachment::fromPath($filename)); + } catch (\Exception $e) { + // Log any issues + $this->grav['log']->error($e->getMessage()); + } + } + } + } + + //fire event to apply optional signers + $this->grav->fireEvent('onEmailMessage', new Event(['message' => $message, 'params' => $params, 'form' => $form])); + + // Send e-mail + $this->email->send($message); + + //fire event after eMail was sent + $this->grav->fireEvent('onEmailSent', new Event(['message' => $message, 'params' => $params, 'form' => $form])); + } + + protected function isAssocArray(array $arr) + { + if (array() === $arr) return false; + $keys = array_keys($arr); + $index_keys = range(0, count($arr) - 1); + return $keys !== $index_keys; + } + +} diff --git a/plugins/email/email.yaml b/plugins/email/email.yaml new file mode 100644 index 0000000..6ddf722 --- /dev/null +++ b/plugins/email/email.yaml @@ -0,0 +1,23 @@ +enabled: true +from: +from_name: +to: +to_name: +queue: + enabled: false + flush_frequency: '* * * * *' + flush_msg_limit: 10 + flush_time_limit: 100 +mailer: + engine: sendmail + smtp: + server: localhost + port: 25 + encryption: none + user: '' + password: '' + auth_mode: '' + sendmail: + bin: '/usr/sbin/sendmail -bs' +content_type: text/html +debug: false \ No newline at end of file diff --git a/plugins/email/hebe.json b/plugins/email/hebe.json new file mode 100644 index 0000000..3831af4 --- /dev/null +++ b/plugins/email/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-email", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/email" + } + ] + } + } + } +} diff --git a/plugins/email/languages.yaml b/plugins/email/languages.yaml new file mode 100644 index 0000000..fa89ac4 --- /dev/null +++ b/plugins/email/languages.yaml @@ -0,0 +1,165 @@ +en: + PLUGIN_EMAIL: + MAIL_ENGINE: "Mail Engine" + MAIL_ENGINE_DISABLED: "Disabled" + CONTENT_TYPE: "Content type" + CONTENT_TYPE_PLAIN_TEXT: "Plain text" + CHARSET: "Charset" + CHARSET_PLACEHOLDER: "Defaults to UTF-8" + EMAIL_FORM: "From address" + EMAIL_FORM_PLACEHOLDER: "Default email from address" + EMAIL_FROM_NAME: "From name" + EMAIL_FROM_NAME_PLACEHOLDER: "Default email from name" + EMAIL_TO: "To address" + EMAIL_TO_PLACEHOLDER: "Default email to address" + EMAIL_TO_NAME: "To name" + EMAIL_TO_NAME_PLACEHOLDER: "Default email to name" + EMAIL_CC: "CC address" + EMAIL_CC_PLACEHOLDER: "Default email CC address" + EMAIL_CC_NAME: "CC name" + EMAIL_CC_NAME_PLACEHOLDER: "Default email CC name" + EMAIL_BCC: "BCC address" + EMAIL_BCC_PLACEHOLDER: "Default email BCC address" + EMAIL_REPLY_TO: "Reply-to address" + EMAIL_REPLY_TO_PLACEHOLDER: "Default email reply-to address" + EMAIL_REPLY_TO_NAME: "Reply-to name" + EMAIL_REPLY_TO_NAME_PLACEHOLDER: "Default email reply-to name" + EMAIL_BODY: "Body" + EMAIL_BODY_PLACEHOLDER: "Defaults to a table of all form fields" + SMTP_SERVER: "SMTP server" + SMTP_SERVER_PLACEHOLDER: "e.g. smtp.google.com" + SMTP_PORT: "SMTP port" + SMTP_PORT_PLACEHOLDER: "Defaults to 25 (plaintext) / 587 (encrypted)" + SMTP_ENCRYPTION: "SMTP encryption" + SMTP_ENCRYPTION_NONE: "None" + SMTP_LOGIN_NAME: "SMTP login name" + SMTP_PASSWORD: "SMTP password" + SMTP_AUTH_MODE: "SMTP auth mode" + PATH_TO_SENDMAIL: "Path to sendmail" + DEBUG: "Debug" + EMAIL_NOT_CONFIGURED: "Email not configured" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Please configure a 'to' address in the Email Plugin settings, or in the form" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Please configure a 'from' address in the Email Plugin settings, or in the form" + TEST_EMAIL_BODY: "

    Testing Email

    This test email has been sent based on the following configuration:

    %1$s

    " + EMAIL_DEFAULTS: "Email Defaults" + SMTP_CONFIGURATION: "SMTP Configuration" + SENDMAIL_CONFIGURATION: "Sendmail Configuration" + ADVANCED: "Advanced" + EMAIL_FOOTER: "GetGrav.org" + QUEUE_TITLE: "Email Queue" + QUEUE_DESC: "When you enable the email queue, email is not sent immediately, rather the email is sent to the queue, and then the Grav scheduler will flush the queue and actually send the email based on the configured frequency. This ensures Grav is not waiting around for email connections to complete." + QUEUE_ENABLED: "Enabled" + QUEUE_FLUSH_FREQUENCY: "Flush Frequency" + QUEUE_FLUSH_FREQUENCY_HELP: "Use 'cron' format" + QUEUE_FLUSH_MSG_LIMIT: "Messages per flush" + QUEUE_FLUSH_MSG_LIMIT_APPEND: "Messages" + QUEUE_FLUSH_TIME_LIMIT: "Flush time limit" + QUEUE_FLUSH_TIME_LIMIT_APPEND: "Seconds" + + + +da: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurere venligst en 'til' email adresse i Email Plugin indstillingerne eller her i formularen" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurere venligst en 'fra' email adresse i Email Plugin indstillingerne eller her i formularen" + +de: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "E-Mail ist nicht konfiguriert" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Bitte konfigurieren sie eine 'An' ('to') Adresse in den Email-Plugin-Einstellungen oder im Formular." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Bitte konfigurieren sie eine 'Von' ('from') Adresse in den Email-Plugin-Einstellungen oder im Formular." + +es: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Por favor configura una dirección de 'remitente' en la configuración del Plugin de Email o en el formulario" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Por favor configura una dirección de 'destinatario' en la configuración del Plugin de Email o en el formulario" + +fr: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "L’e-mail n’est pas configuré" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Veuillez configurer une adresse de 'destinataire' dans les paramètres du Plugin ou dans le formulaire." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Veuillez configurer une adresse 'd'expéditeur' dans les paramètres du Plugin ou dans le formulaire." + TEST_EMAIL_BODY: "

    E-mail de test

    Cet e-mail de test est basé sur la configuration suivante :

    %1$s

    " + EMAIL_FOOTER: "GetGrav.org" + +hr: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Email nije konfiguriran" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurirajte 'za' ('to') adresu u postavkama Email dodatka ili u obrascu" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurirajte 'od' ('from') adresu u postavkama Email dodatka ili u obrascu" + +it: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Per favore, configura l'indirizzo di destinazione ('to') nella configurazione del Plugin Email, oppure direttamente nella form." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Per favore, configura l'indirizzo di provenienza ('from') nella configurazione del Plugin Email, oppure direttamente nella form" + +ro: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Adresa de email nu este configurată" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Vă rugam setați o adresă 'către' în setările modulului Email sau în formular" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Vă rugam setați o adresă 'de la' în setările modulului Email sau în formular" + +ru: + PLUGIN_EMAIL: + MAIL_ENGINE: "Почтовая система" + MAIL_ENGINE_DISABLED: "Отключена" + CONTENT_TYPE: "Тип контента" + CONTENT_TYPE_PLAIN_TEXT: "Простой текст" + CHARSET: "Кодировка" + CHARSET_PLACEHOLDER: "По умолчанию UTF-8" + EMAIL_DEFAULTS: "Email настройки по умолчанию" + EMAIL_FORM: "Почта отправителя" + EMAIL_FORM_PLACEHOLDER: "Email адрес отправителя по умолчанию" + EMAIL_FROM_NAME: "Имя почты отправителя" + EMAIL_FROM_NAME_PLACEHOLDER: "Email имя отправителя по умолчанию" + EMAIL_TO: "Почта получателя" + EMAIL_TO_PLACEHOLDER: "Email адрес получателя по умолчанию" + EMAIL_TO_NAME: "Имя почты получателя" + EMAIL_TO_NAME_PLACEHOLDER: "Email имя получателя по умолчанию" + EMAIL_CC: "Почта CC" + EMAIL_CC_PLACEHOLDER: "Email CC адрес по умолчанию" + EMAIL_CC_NAME: "Имя почты CC" + EMAIL_CC_NAME_PLACEHOLDER: "Email CC имя по умолчанию" + EMAIL_BCC: "Почта BCC" + EMAIL_BCC_PLACEHOLDER: "Email BCC адрес по умолчанию" + EMAIL_REPLY_TO: "Почта для ответов" + EMAIL_REPLY_TO_PLACEHOLDER: "Email для ответов адрес по умолчанию" + EMAIL_REPLY_TO_NAME: "Имя почты для ответов" + EMAIL_REPLY_TO_NAME_PLACEHOLDER: "Email для ответов имя по умолчанию" + EMAIL_BODY: "Тело сообщения" + EMAIL_BODY_PLACEHOLDER: "По умолчанию используется таблица всех полей формы" + SMTP_CONFIGURATION: "Конфигурация SMTP" + SMTP_SERVER: "SMTP сервер" + SMTP_SERVER_PLACEHOLDER: "e.g. smtp.google.com" + SMTP_PORT: "SMTP порт" + SMTP_PORT_PLACEHOLDER: "По умолчанию 25 (plaintext) / 587 (encrypted)" + SMTP_ENCRYPTION: "SMTP шифрование" + SMTP_ENCRYPTION_NONE: "Нет" + SMTP_LOGIN_NAME: "SMTP логин" + SMTP_PASSWORD: "SMTP пароль" + SENDMAIL_CONFIGURATION: "Конфигурация Sendmail" + PATH_TO_SENDMAIL: "Путь к sendmail" + QUEUE_TITLE: "Очередь Email" + QUEUE_DESC: "Когда вы включаете очередь email, электронная почта не отправляется немедленно, а отправляется в очередь, затем планировщик Grav обрабатывает очередь и на основе настроенной частоты фактически отправляет электронную почту. Это гарантирует, что Grav не ждет завершения подключения к электронной почте." + QUEUE_ENABLED: "Включено" + QUEUE_FLUSH_FREQUENCY: "Частота обраблтки" + QUEUE_FLUSH_FREQUENCY_HELP: "Использовать формат 'cron'" + QUEUE_FLUSH_MSG_LIMIT: "Количество сообщений на задачу" + QUEUE_FLUSH_MSG_LIMIT_APPEND: "Сообщений" + QUEUE_FLUSH_TIME_LIMIT: "Ограничение времени обработки" + QUEUE_FLUSH_TIME_LIMIT_APPEND: "Секунд" + ADVANCED: "Расширенные" + DEBUG: "Отладка" + EMAIL_NOT_CONFIGURED: "Электронная почта не настроена" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Пожалуйста настройте адрес получателя ('to') в настройках плагина Email Plugin, или на форме" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Пожалуйста настройте адрес отправителя ('from') в настройках плагина Email Plugin, или на форме" + TEST_EMAIL_BODY: "

    Тестирование электронной почты

    Это тестовое письмо отправлено на основе следующей конфигурации:

    %1$s

    " + EMAIL_FOOTER: "GetGrav.org" + +uk: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Електронна пошта не налаштована" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Будь ласка налаштуйте адресу одержувача ('to') в налаштуваннях плагіна Email Plugin, або на формі" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Будь ласка налаштуйте адресу відправника ('from') в налаштуваннях плагіна Email Plugin, або на формі" + TEST_EMAIL_BODY: "

    Тестування електронної пошти

    Це тестовий лист відправлено на основі такої конфігурації:

    %1$s

    " + EMAIL_FOOTER: "GetGrav.org" diff --git a/plugins/email/templates/email/base.html.twig b/plugins/email/templates/email/base.html.twig new file mode 100644 index 0000000..0dd827f --- /dev/null +++ b/plugins/email/templates/email/base.html.twig @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + {% block content %} + {{ content|raw }} + {% endblock content %} +
    +
    +
    + + + + + + + + + + + + + + diff --git a/plugins/email/vendor/autoload.php b/plugins/email/vendor/autoload.php new file mode 100644 index 0000000..26a2db7 --- /dev/null +++ b/plugins/email/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/plugins/email/vendor/composer/InstalledVersions.php b/plugins/email/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..4411bc9 --- /dev/null +++ b/plugins/email/vendor/composer/InstalledVersions.php @@ -0,0 +1,291 @@ + + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '22b33105768561e48c79c283cb2e7d960f89558f', + 'name' => 'getgrav/grav-plugin-email', + ), + 'versions' => + array ( + 'doctrine/lexer' => + array ( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', + ), + 'egulias/email-validator' => + array ( + 'pretty_version' => '2.1.25', + 'version' => '2.1.25.0', + 'aliases' => + array ( + ), + 'reference' => '0dbf5d78455d4d6a41d186da50adc1122ec066f4', + ), + 'getgrav/grav-plugin-email' => + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '22b33105768561e48c79c283cb2e7d960f89558f', + ), + 'swiftmailer/swiftmailer' => + array ( + 'pretty_version' => 'v6.2.5', + 'version' => '6.2.5.0', + 'aliases' => + array ( + ), + 'reference' => '698a6a9f54d7eb321274de3ad19863802c879fb7', + ), + 'symfony/polyfill-iconv' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6', + ), + 'symfony/polyfill-intl-idn' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => '0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44', + ), + 'symfony/polyfill-intl-normalizer' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => '6e971c891537eb617a00bb07a43d182a6915faba', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f377a3dd1fde44d37b9831d68dc8dea3ffd28e13', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9', + ), + ), +); + + + + + + + +public static function getInstalledPackages() +{ +return array_keys(self::$installed['versions']); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +return isset(self::$installed['versions'][$packageName]); +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +$ranges = array(); +if (isset(self::$installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = self::$installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + + + + + +public static function getVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['version']; +} + + + + + +public static function getPrettyVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['pretty_version']; +} + + + + + +public static function getReference($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['reference'])) { +return null; +} + +return self::$installed['versions'][$packageName]['reference']; +} + + + + + +public static function getRootPackage() +{ +return self::$installed['root']; +} + + + + + + + +public static function getRawData() +{ +return self::$installed; +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +} +} diff --git a/plugins/email/vendor/composer/LICENSE b/plugins/email/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/plugins/email/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/plugins/email/vendor/composer/autoload_classmap.php b/plugins/email/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..17e18aa --- /dev/null +++ b/plugins/email/vendor/composer/autoload_classmap.php @@ -0,0 +1,12 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Grav\\Plugin\\EmailPlugin' => $baseDir . '/email.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', +); diff --git a/plugins/email/vendor/composer/autoload_files.php b/plugins/email/vendor/composer/autoload_files.php new file mode 100644 index 0000000..ac0724d --- /dev/null +++ b/plugins/email/vendor/composer/autoload_files.php @@ -0,0 +1,15 @@ + $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', + 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', +); diff --git a/plugins/email/vendor/composer/autoload_namespaces.php b/plugins/email/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/plugins/email/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php72'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), + 'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'), + 'Grav\\Plugin\\Email\\' => array($baseDir . '/classes'), + 'Grav\\Plugin\\Console\\' => array($baseDir . '/cli'), + 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), +); diff --git a/plugins/email/vendor/composer/autoload_real.php b/plugins/email/vendor/composer/autoload_real.php new file mode 100644 index 0000000..6d6d28c --- /dev/null +++ b/plugins/email/vendor/composer/autoload_real.php @@ -0,0 +1,75 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire73924571ea6ee98bb12d10ff20aff2ab($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire73924571ea6ee98bb12d10ff20aff2ab($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/plugins/email/vendor/composer/autoload_static.php b/plugins/email/vendor/composer/autoload_static.php new file mode 100644 index 0000000..bea65a0 --- /dev/null +++ b/plugins/email/vendor/composer/autoload_static.php @@ -0,0 +1,96 @@ + __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', + 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php72\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, + 'Symfony\\Polyfill\\Iconv\\' => 23, + ), + 'G' => + array ( + 'Grav\\Plugin\\Email\\' => 18, + 'Grav\\Plugin\\Console\\' => 20, + ), + 'E' => + array ( + 'Egulias\\EmailValidator\\' => 23, + ), + 'D' => + array ( + 'Doctrine\\Common\\Lexer\\' => 22, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Idn\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', + ), + 'Symfony\\Polyfill\\Iconv\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-iconv', + ), + 'Grav\\Plugin\\Email\\' => + array ( + 0 => __DIR__ . '/../..' . '/classes', + ), + 'Grav\\Plugin\\Console\\' => + array ( + 0 => __DIR__ . '/../..' . '/cli', + ), + 'Egulias\\EmailValidator\\' => + array ( + 0 => __DIR__ . '/..' . '/egulias/email-validator/src', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Grav\\Plugin\\EmailPlugin' => __DIR__ . '/../..' . '/email.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/email/vendor/composer/installed.json b/plugins/email/vendor/composer/installed.json new file mode 100644 index 0000000..2a72e0b --- /dev/null +++ b/plugins/email/vendor/composer/installed.json @@ -0,0 +1,660 @@ +{ + "packages": [ + { + "name": "doctrine/lexer", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "time": "2020-05-25T17:44:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "install-path": "../doctrine/lexer" + }, + { + "name": "egulias/email-validator", + "version": "2.1.25", + "version_normalized": "2.1.25.0", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "time": "2020-12-29T14:50:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "install-path": "../egulias/email-validator" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.2.5", + "version_normalized": "6.2.5.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.0", + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "symfony/phpunit-bridge": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses" + }, + "time": "2021-01-12T09:35:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.5" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "type": "tidelift" + } + ], + "install-path": "../swiftmailer/swiftmailer" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6", + "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "time": "2021-01-07T16:49:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-iconv" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2021-01-07T16:49:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-idn" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2021-01-07T17:09:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2021-01-07T16:49:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.22.0", + "version_normalized": "1.22.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2021-01-07T16:49:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php72" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/plugins/email/vendor/composer/installed.php b/plugins/email/vendor/composer/installed.php new file mode 100644 index 0000000..9c251d1 --- /dev/null +++ b/plugins/email/vendor/composer/installed.php @@ -0,0 +1,96 @@ + + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '22b33105768561e48c79c283cb2e7d960f89558f', + 'name' => 'getgrav/grav-plugin-email', + ), + 'versions' => + array ( + 'doctrine/lexer' => + array ( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', + ), + 'egulias/email-validator' => + array ( + 'pretty_version' => '2.1.25', + 'version' => '2.1.25.0', + 'aliases' => + array ( + ), + 'reference' => '0dbf5d78455d4d6a41d186da50adc1122ec066f4', + ), + 'getgrav/grav-plugin-email' => + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '22b33105768561e48c79c283cb2e7d960f89558f', + ), + 'swiftmailer/swiftmailer' => + array ( + 'pretty_version' => 'v6.2.5', + 'version' => '6.2.5.0', + 'aliases' => + array ( + ), + 'reference' => '698a6a9f54d7eb321274de3ad19863802c879fb7', + ), + 'symfony/polyfill-iconv' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6', + ), + 'symfony/polyfill-intl-idn' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => '0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44', + ), + 'symfony/polyfill-intl-normalizer' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => '6e971c891537eb617a00bb07a43d182a6915faba', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'f377a3dd1fde44d37b9831d68dc8dea3ffd28e13', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.22.0', + 'version' => '1.22.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9', + ), + ), +); diff --git a/plugins/email/vendor/composer/platform_check.php b/plugins/email/vendor/composer/platform_check.php new file mode 100644 index 0000000..589e9e7 --- /dev/null +++ b/plugins/email/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/plugins/email/vendor/doctrine/lexer/LICENSE b/plugins/email/vendor/doctrine/lexer/LICENSE new file mode 100644 index 0000000..e8fdec4 --- /dev/null +++ b/plugins/email/vendor/doctrine/lexer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2018 Doctrine Project + +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/plugins/email/vendor/doctrine/lexer/README.md b/plugins/email/vendor/doctrine/lexer/README.md new file mode 100644 index 0000000..e1b419a --- /dev/null +++ b/plugins/email/vendor/doctrine/lexer/README.md @@ -0,0 +1,9 @@ +# Doctrine Lexer + +Build Status: [![Build Status](https://travis-ci.org/doctrine/lexer.svg?branch=master)](https://travis-ci.org/doctrine/lexer) + +Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. + +This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). + +https://www.doctrine-project.org/projects/lexer.html diff --git a/plugins/email/vendor/doctrine/lexer/composer.json b/plugins/email/vendor/doctrine/lexer/composer.json new file mode 100644 index 0000000..3432bae --- /dev/null +++ b/plugins/email/vendor/doctrine/lexer/composer.json @@ -0,0 +1,41 @@ +{ + "name": "doctrine/lexer", + "type": "library", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "keywords": [ + "php", + "parser", + "lexer", + "annotations", + "docblock" + ], + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/plugins/email/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php b/plugins/email/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php new file mode 100644 index 0000000..385643a --- /dev/null +++ b/plugins/email/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php @@ -0,0 +1,328 @@ +input = $input; + $this->tokens = []; + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param int $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @param int $position + * + * @return string + */ + public function getInputUntilPosition($position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param int|string $token + * + * @return bool + */ + public function isNextToken($token) + { + return $this->lookahead !== null && $this->lookahead['type'] === $token; + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param array $tokens + * + * @return bool + */ + public function isNextTokenAny(array $tokens) + { + return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true); + } + + /** + * Moves to the next token in the input string. + * + * @return bool + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = isset($this->tokens[$this->position]) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param string $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param mixed $value + * @param int|string $token + * + * @return bool + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + if (! isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers() + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($this->regex, $input, -1, $flags); + + if ($matches === false) { + // Work around https://bugs.php.net/78122 + $matches = [[$input, 0]]; + } + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = [ + 'value' => $match[0], + 'type' => $type, + 'position' => $match[1], + ]; + } + } + + /** + * Gets the literal for a given token. + * + * @param int|string $token + * + * @return int|string + */ + public function getLiteral($token) + { + $className = static::class; + $reflClass = new ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } + + /** + * Lexical catchable patterns. + * + * @return array + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return array + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return int|string|null + */ + abstract protected function getType(&$value); +} diff --git a/plugins/email/vendor/egulias/email-validator/LICENSE b/plugins/email/vendor/egulias/email-validator/LICENSE new file mode 100644 index 0000000..c34d2c1 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2016 Eduardo Gulias Davis + +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/plugins/email/vendor/egulias/email-validator/composer.json b/plugins/email/vendor/egulias/email-validator/composer.json new file mode 100644 index 0000000..a275696 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/composer.json @@ -0,0 +1,38 @@ +{ + "name": "egulias/email-validator", + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"], + "license": "MIT", + "authors": [ + {"name": "Eduardo Gulias Davis"} + ], + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "require": { + "php": ">=5.5", + "doctrine/lexer": "^1.0.1", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Egulias\\EmailValidator\\Tests\\": "tests" + } + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/EmailLexer.php b/plugins/email/vendor/egulias/email-validator/src/EmailLexer.php new file mode 100644 index 0000000..59dcd58 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/EmailLexer.php @@ -0,0 +1,283 @@ + self::S_OPENPARENTHESIS, + ')' => self::S_CLOSEPARENTHESIS, + '<' => self::S_LOWERTHAN, + '>' => self::S_GREATERTHAN, + '[' => self::S_OPENBRACKET, + ']' => self::S_CLOSEBRACKET, + ':' => self::S_COLON, + ';' => self::S_SEMICOLON, + '@' => self::S_AT, + '\\' => self::S_BACKSLASH, + '/' => self::S_SLASH, + ',' => self::S_COMMA, + '.' => self::S_DOT, + "'" => self::S_SQUOTE, + "`" => self::S_BACKTICK, + '"' => self::S_DQUOTE, + '-' => self::S_HYPHEN, + '::' => self::S_DOUBLECOLON, + ' ' => self::S_SP, + "\t" => self::S_HTAB, + "\r" => self::S_CR, + "\n" => self::S_LF, + "\r\n" => self::CRLF, + 'IPv6' => self::S_IPV6TAG, + '{' => self::S_OPENQBRACKET, + '}' => self::S_CLOSEQBRACKET, + '' => self::S_EMPTY, + '\0' => self::C_NUL, + ); + + /** + * @var bool + */ + protected $hasInvalidTokens = false; + + /** + * @var array + * + * @psalm-var array{value:string, type:null|int, position:int}|array + */ + protected $previous = []; + + /** + * The last matched/seen token. + * + * @var array + * + * @psalm-var array{value:string, type:null|int, position:int} + */ + public $token; + + /** + * The next token in the input. + * + * @var array|null + */ + public $lookahead; + + /** + * @psalm-var array{value:'', type:null, position:0} + */ + private static $nullToken = [ + 'value' => '', + 'type' => null, + 'position' => 0, + ]; + + public function __construct() + { + $this->previous = $this->token = self::$nullToken; + $this->lookahead = null; + } + + /** + * @return void + */ + public function reset() + { + $this->hasInvalidTokens = false; + parent::reset(); + $this->previous = $this->token = self::$nullToken; + } + + /** + * @return bool + */ + public function hasInvalidTokens() + { + return $this->hasInvalidTokens; + } + + /** + * @param int $type + * @throws \UnexpectedValueException + * @return boolean + * + * @psalm-suppress InvalidScalarArgument + */ + public function find($type) + { + $search = clone $this; + $search->skipUntil($type); + + if (!$search->lookahead) { + throw new \UnexpectedValueException($type . ' not found'); + } + return true; + } + + /** + * getPrevious + * + * @return array + */ + public function getPrevious() + { + return $this->previous; + } + + /** + * moveNext + * + * @return boolean + */ + public function moveNext() + { + $this->previous = $this->token; + $hasNext = parent::moveNext(); + $this->token = $this->token ?: self::$nullToken; + + return $hasNext; + } + + /** + * Lexical catchable patterns. + * + * @return string[] + */ + protected function getCatchablePatterns() + { + return array( + '[a-zA-Z_]+[46]?', //ASCII and domain literal + '[^\x00-\x7F]', //UTF-8 + '[0-9]+', + '\r\n', + '::', + '\s+?', + '.', + ); + } + + /** + * Lexical non-catchable patterns. + * + * @return string[] + */ + protected function getNonCatchablePatterns() + { + return array('[\xA0-\xff]+'); + } + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * @throws \InvalidArgumentException + * @return integer + */ + protected function getType(&$value) + { + if ($this->isNullType($value)) { + return self::C_NUL; + } + + if ($this->isValid($value)) { + return $this->charValue[$value]; + } + + if ($this->isUTF8Invalid($value)) { + $this->hasInvalidTokens = true; + return self::INVALID; + } + + return self::GENERIC; + } + + /** + * @param string $value + * + * @return bool + */ + protected function isValid($value) + { + if (isset($this->charValue[$value])) { + return true; + } + + return false; + } + + /** + * @param string $value + * @return bool + */ + protected function isNullType($value) + { + if ($value === "\0") { + return true; + } + + return false; + } + + /** + * @param string $value + * @return bool + */ + protected function isUTF8Invalid($value) + { + if (preg_match('/\p{Cc}+/u', $value)) { + return true; + } + + return false; + } + + /** + * @return string + */ + protected function getModifiers() + { + return 'iu'; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/EmailParser.php b/plugins/email/vendor/egulias/email-validator/src/EmailParser.php new file mode 100644 index 0000000..6b7bad6 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/EmailParser.php @@ -0,0 +1,137 @@ + + */ +class EmailParser +{ + const EMAIL_MAX_LENGTH = 254; + + /** + * @var array + */ + protected $warnings = []; + + /** + * @var string + */ + protected $domainPart = ''; + + /** + * @var string + */ + protected $localPart = ''; + /** + * @var EmailLexer + */ + protected $lexer; + + /** + * @var LocalPart + */ + protected $localPartParser; + + /** + * @var DomainPart + */ + protected $domainPartParser; + + public function __construct(EmailLexer $lexer) + { + $this->lexer = $lexer; + $this->localPartParser = new LocalPart($this->lexer); + $this->domainPartParser = new DomainPart($this->lexer); + } + + /** + * @param string $str + * @return array + */ + public function parse($str) + { + $this->lexer->setInput($str); + + if (!$this->hasAtToken()) { + throw new NoLocalPart(); + } + + + $this->localPartParser->parse($str); + $this->domainPartParser->parse($str); + + $this->setParts($str); + + if ($this->lexer->hasInvalidTokens()) { + throw new ExpectingATEXT(); + } + + return array('local' => $this->localPart, 'domain' => $this->domainPart); + } + + /** + * @return Warning\Warning[] + */ + public function getWarnings() + { + $localPartWarnings = $this->localPartParser->getWarnings(); + $domainPartWarnings = $this->domainPartParser->getWarnings(); + $this->warnings = array_merge($localPartWarnings, $domainPartWarnings); + + $this->addLongEmailWarning($this->localPart, $this->domainPart); + + return $this->warnings; + } + + /** + * @return string + */ + public function getParsedDomainPart() + { + return $this->domainPart; + } + + /** + * @param string $email + */ + protected function setParts($email) + { + $parts = explode('@', $email); + $this->domainPart = $this->domainPartParser->getDomainPart(); + $this->localPart = $parts[0]; + } + + /** + * @return bool + */ + protected function hasAtToken() + { + $this->lexer->moveNext(); + $this->lexer->moveNext(); + if ($this->lexer->token['type'] === EmailLexer::S_AT) { + return false; + } + + return true; + } + + /** + * @param string $localPart + * @param string $parsedDomainPart + */ + protected function addLongEmailWarning($localPart, $parsedDomainPart) + { + if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) { + $this->warnings[EmailTooLong::CODE] = new EmailTooLong(); + } + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/EmailValidator.php b/plugins/email/vendor/egulias/email-validator/src/EmailValidator.php new file mode 100644 index 0000000..a30f21d --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/EmailValidator.php @@ -0,0 +1,67 @@ +lexer = new EmailLexer(); + } + + /** + * @param string $email + * @param EmailValidation $emailValidation + * @return bool + */ + public function isValid($email, EmailValidation $emailValidation) + { + $isValid = $emailValidation->isValid($email, $this->lexer); + $this->warnings = $emailValidation->getWarnings(); + $this->error = $emailValidation->getError(); + + return $isValid; + } + + /** + * @return boolean + */ + public function hasWarnings() + { + return !empty($this->warnings); + } + + /** + * @return array + */ + public function getWarnings() + { + return $this->warnings; + } + + /** + * @return InvalidEmail|null + */ + public function getError() + { + return $this->error; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Exception/AtextAfterCFWS.php b/plugins/email/vendor/egulias/email-validator/src/Exception/AtextAfterCFWS.php new file mode 100644 index 0000000..97f41a2 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Exception/AtextAfterCFWS.php @@ -0,0 +1,9 @@ +lexer->moveNext(); + + $this->performDomainStartChecks(); + + $domain = $this->doParseDomainPart(); + + $prev = $this->lexer->getPrevious(); + $length = strlen($domain); + + if ($prev['type'] === EmailLexer::S_DOT) { + throw new DotAtEnd(); + } + if ($prev['type'] === EmailLexer::S_HYPHEN) { + throw new DomainHyphened(); + } + if ($length > self::DOMAIN_MAX_LENGTH) { + $this->warnings[DomainTooLong::CODE] = new DomainTooLong(); + } + if ($prev['type'] === EmailLexer::S_CR) { + throw new CRLFAtTheEnd(); + } + $this->domainPart = $domain; + } + + private function performDomainStartChecks() + { + $this->checkInvalidTokensAfterAT(); + $this->checkEmptyDomain(); + + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { + $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment(); + $this->parseDomainComments(); + } + } + + private function checkEmptyDomain() + { + $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY || + ($this->lexer->token['type'] === EmailLexer::S_SP && + !$this->lexer->isNextToken(EmailLexer::GENERIC)); + + if ($thereIsNoDomain) { + throw new NoDomainPart(); + } + } + + private function checkInvalidTokensAfterAT() + { + if ($this->lexer->token['type'] === EmailLexer::S_DOT) { + throw new DotAtStart(); + } + if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) { + throw new DomainHyphened(); + } + } + + /** + * @return string + */ + public function getDomainPart() + { + return $this->domainPart; + } + + /** + * @param string $addressLiteral + * @param int $maxGroups + */ + public function checkIPV6Tag($addressLiteral, $maxGroups = 8) + { + $prev = $this->lexer->getPrevious(); + if ($prev['type'] === EmailLexer::S_COLON) { + $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd(); + } + + $IPv6 = substr($addressLiteral, 5); + //Daniel Marschall's new IPv6 testing strategy + $matchesIP = explode(':', $IPv6); + $groupCount = count($matchesIP); + $colons = strpos($IPv6, '::'); + + if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) { + $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar(); + } + + if ($colons === false) { + // We need exactly the right number of groups + if ($groupCount !== $maxGroups) { + $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount(); + } + return; + } + + if ($colons !== strrpos($IPv6, '::')) { + $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon(); + return; + } + + if ($colons === 0 || $colons === (strlen($IPv6) - 2)) { + // RFC 4291 allows :: at the start or end of an address + //with 7 other groups in addition + ++$maxGroups; + } + + if ($groupCount > $maxGroups) { + $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups(); + } elseif ($groupCount === $maxGroups) { + $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated(); + } + } + + /** + * @return string + */ + protected function doParseDomainPart() + { + $domain = ''; + $label = ''; + $openedParenthesis = 0; + do { + $prev = $this->lexer->getPrevious(); + + $this->checkNotAllowedChars($this->lexer->token); + + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { + $this->parseComments(); + $openedParenthesis += $this->getOpenedParenthesis(); + $this->lexer->moveNext(); + $tmpPrev = $this->lexer->getPrevious(); + if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + $openedParenthesis--; + } + } + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + if ($openedParenthesis === 0) { + throw new UnopenedComment(); + } else { + $openedParenthesis--; + } + } + + $this->checkConsecutiveDots(); + $this->checkDomainPartExceptions($prev); + + if ($this->hasBrackets()) { + $this->parseDomainLiteral(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_DOT) { + $this->checkLabelLength($label); + $label = ''; + } else { + $label .= $this->lexer->token['value']; + } + + if ($this->isFWS()) { + $this->parseFWS(); + } + + $domain .= $this->lexer->token['value']; + $this->lexer->moveNext(); + if ($this->lexer->token['type'] === EmailLexer::S_SP) { + throw new CharNotAllowed(); + } + } while (null !== $this->lexer->token['type']); + + $this->checkLabelLength($label); + + return $domain; + } + + private function checkNotAllowedChars(array $token) + { + $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true]; + if (isset($notAllowed[$token['type']])) { + throw new CharNotAllowed(); + } + } + + /** + * @return string|false + */ + protected function parseDomainLiteral() + { + if ($this->lexer->isNextToken(EmailLexer::S_COLON)) { + $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); + } + if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) { + $lexer = clone $this->lexer; + $lexer->moveNext(); + if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) { + $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart(); + } + } + + return $this->doParseDomainLiteral(); + } + + /** + * @return string|false + */ + protected function doParseDomainLiteral() + { + $IPv6TAG = false; + $addressLiteral = ''; + do { + if ($this->lexer->token['type'] === EmailLexer::C_NUL) { + throw new ExpectingDTEXT(); + } + + if ($this->lexer->token['type'] === EmailLexer::INVALID || + $this->lexer->token['type'] === EmailLexer::C_DEL || + $this->lexer->token['type'] === EmailLexer::S_LF + ) { + $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); + } + + if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) { + throw new ExpectingDTEXT(); + } + + if ($this->lexer->isNextTokenAny( + array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) + )) { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + $this->parseFWS(); + } + + if ($this->lexer->isNextToken(EmailLexer::S_CR)) { + throw new CRNoLF(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) { + $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); + $addressLiteral .= $this->lexer->token['value']; + $this->lexer->moveNext(); + $this->validateQuotedPair(); + } + if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) { + $IPv6TAG = true; + } + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) { + break; + } + + $addressLiteral .= $this->lexer->token['value']; + + } while ($this->lexer->moveNext()); + + $addressLiteral = str_replace('[', '', $addressLiteral); + $addressLiteral = $this->checkIPV4Tag($addressLiteral); + + if (false === $addressLiteral) { + return $addressLiteral; + } + + if (!$IPv6TAG) { + $this->warnings[DomainLiteral::CODE] = new DomainLiteral(); + return $addressLiteral; + } + + $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); + + $this->checkIPV6Tag($addressLiteral); + + return $addressLiteral; + } + + /** + * @param string $addressLiteral + * + * @return string|false + */ + protected function checkIPV4Tag($addressLiteral) + { + $matchesIP = array(); + + // Extract IPv4 part from the end of the address-literal (if there is one) + if (preg_match( + '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', + $addressLiteral, + $matchesIP + ) > 0 + ) { + $index = strrpos($addressLiteral, $matchesIP[0]); + if ($index === 0) { + $this->warnings[AddressLiteral::CODE] = new AddressLiteral(); + return false; + } + // Convert IPv4 part to IPv6 format for further testing + $addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0'; + } + + return $addressLiteral; + } + + protected function checkDomainPartExceptions(array $prev) + { + $invalidDomainTokens = array( + EmailLexer::S_DQUOTE => true, + EmailLexer::S_SQUOTE => true, + EmailLexer::S_BACKTICK => true, + EmailLexer::S_SEMICOLON => true, + EmailLexer::S_GREATERTHAN => true, + EmailLexer::S_LOWERTHAN => true, + ); + + if (isset($invalidDomainTokens[$this->lexer->token['type']])) { + throw new ExpectingATEXT(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_COMMA) { + throw new CommaInDomain(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_AT) { + throw new ConsecutiveAt(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) { + throw new ExpectingATEXT(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) { + throw new DomainHyphened(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH + && $this->lexer->isNextToken(EmailLexer::GENERIC)) { + throw new ExpectingATEXT(); + } + } + + /** + * @return bool + */ + protected function hasBrackets() + { + if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) { + return false; + } + + try { + $this->lexer->find(EmailLexer::S_CLOSEBRACKET); + } catch (\RuntimeException $e) { + throw new ExpectingDomainLiteralClose(); + } + + return true; + } + + /** + * @param string $label + */ + protected function checkLabelLength($label) + { + if ($this->isLabelTooLong($label)) { + $this->warnings[LabelTooLong::CODE] = new LabelTooLong(); + } + } + + /** + * @param string $label + * @return bool + */ + private function isLabelTooLong($label) + { + if (preg_match('/[^\x00-\x7F]/', $label)) { + idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo); + + return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG); + } + + return strlen($label) > self::LABEL_MAX_LENGTH; + } + + protected function parseDomainComments() + { + $this->isUnclosedComment(); + while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { + $this->warnEscaping(); + $this->lexer->moveNext(); + } + + $this->lexer->moveNext(); + if ($this->lexer->isNextToken(EmailLexer::S_DOT)) { + throw new ExpectingATEXT(); + } + } + + protected function addTLDWarnings() + { + if ($this->warnings[DomainLiteral::CODE]) { + $this->warnings[TLD::CODE] = new TLD(); + } + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Parser/LocalPart.php b/plugins/email/vendor/egulias/email-validator/src/Parser/LocalPart.php new file mode 100644 index 0000000..3c21f34 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Parser/LocalPart.php @@ -0,0 +1,145 @@ +lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) { + if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) { + throw new DotAtStart(); + } + + $closingQuote = $this->checkDQUOTE($closingQuote); + if ($closingQuote && $parseDQuote) { + $parseDQuote = $this->parseDoubleQuote(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) { + $this->parseComments(); + $openedParenthesis += $this->getOpenedParenthesis(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) { + if ($openedParenthesis === 0) { + throw new UnopenedComment(); + } + + $openedParenthesis--; + } + + $this->checkConsecutiveDots(); + + if ($this->lexer->token['type'] === EmailLexer::S_DOT && + $this->lexer->isNextToken(EmailLexer::S_AT) + ) { + throw new DotAtEnd(); + } + + $this->warnEscaping(); + $this->isInvalidToken($this->lexer->token, $closingQuote); + + if ($this->isFWS()) { + $this->parseFWS(); + } + + $totalLength += strlen($this->lexer->token['value']); + $this->lexer->moveNext(); + } + + if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) { + $this->warnings[LocalTooLong::CODE] = new LocalTooLong(); + } + } + + /** + * @return bool + */ + protected function parseDoubleQuote() + { + $parseAgain = true; + $special = array( + EmailLexer::S_CR => true, + EmailLexer::S_HTAB => true, + EmailLexer::S_LF => true + ); + + $invalid = array( + EmailLexer::C_NUL => true, + EmailLexer::S_HTAB => true, + EmailLexer::S_CR => true, + EmailLexer::S_LF => true + ); + $setSpecialsWarning = true; + + $this->lexer->moveNext(); + + while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) { + $parseAgain = false; + if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + $setSpecialsWarning = false; + } + if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) { + $this->lexer->moveNext(); + } + + $this->lexer->moveNext(); + + if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) { + throw new ExpectingATEXT(); + } + } + + $prev = $this->lexer->getPrevious(); + + if ($prev['type'] === EmailLexer::S_BACKSLASH) { + if (!$this->checkDQUOTE(false)) { + throw new UnclosedQuotedString(); + } + } + + if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) { + throw new ExpectingAT(); + } + + return $parseAgain; + } + + /** + * @param bool $closingQuote + */ + protected function isInvalidToken(array $token, $closingQuote) + { + $forbidden = array( + EmailLexer::S_COMMA, + EmailLexer::S_CLOSEBRACKET, + EmailLexer::S_OPENBRACKET, + EmailLexer::S_GREATERTHAN, + EmailLexer::S_LOWERTHAN, + EmailLexer::S_COLON, + EmailLexer::S_SEMICOLON, + EmailLexer::INVALID + ); + + if (in_array($token['type'], $forbidden) && !$closingQuote) { + throw new ExpectingATEXT(); + } + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Parser/Parser.php b/plugins/email/vendor/egulias/email-validator/src/Parser/Parser.php new file mode 100644 index 0000000..ccdc938 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Parser/Parser.php @@ -0,0 +1,249 @@ +lexer = $lexer; + } + + /** + * @return \Egulias\EmailValidator\Warning\Warning[] + */ + public function getWarnings() + { + return $this->warnings; + } + + /** + * @param string $str + */ + abstract public function parse($str); + + /** @return int */ + public function getOpenedParenthesis() + { + return $this->openedParenthesis; + } + + /** + * validateQuotedPair + */ + protected function validateQuotedPair() + { + if (!($this->lexer->token['type'] === EmailLexer::INVALID + || $this->lexer->token['type'] === EmailLexer::C_DEL)) { + throw new ExpectingQPair(); + } + + $this->warnings[QuotedPart::CODE] = + new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); + } + + protected function parseComments() + { + $this->openedParenthesis = 1; + $this->isUnclosedComment(); + $this->warnings[Comment::CODE] = new Comment(); + while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) { + if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) { + $this->openedParenthesis++; + } + $this->warnEscaping(); + $this->lexer->moveNext(); + } + + $this->lexer->moveNext(); + if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) { + throw new ExpectingATEXT(); + } + + if ($this->lexer->isNextToken(EmailLexer::S_AT)) { + $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); + } + } + + /** + * @return bool + */ + protected function isUnclosedComment() + { + try { + $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS); + return true; + } catch (\RuntimeException $e) { + throw new UnclosedComment(); + } + } + + protected function parseFWS() + { + $previous = $this->lexer->getPrevious(); + + $this->checkCRLFInFWS(); + + if ($this->lexer->token['type'] === EmailLexer::S_CR) { + throw new CRNoLF(); + } + + if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) { + throw new AtextAfterCFWS(); + } + + if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) { + throw new ExpectingCTEXT(); + } + + if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) { + $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt(); + } else { + $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); + } + } + + protected function checkConsecutiveDots() + { + if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) { + throw new ConsecutiveDot(); + } + } + + /** + * @return bool + */ + protected function isFWS() + { + if ($this->escaped()) { + return false; + } + + if ($this->lexer->token['type'] === EmailLexer::S_SP || + $this->lexer->token['type'] === EmailLexer::S_HTAB || + $this->lexer->token['type'] === EmailLexer::S_CR || + $this->lexer->token['type'] === EmailLexer::S_LF || + $this->lexer->token['type'] === EmailLexer::CRLF + ) { + return true; + } + + return false; + } + + /** + * @return bool + */ + protected function escaped() + { + $previous = $this->lexer->getPrevious(); + + if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH + && + $this->lexer->token['type'] !== EmailLexer::GENERIC + ) { + return true; + } + + return false; + } + + /** + * @return bool + */ + protected function warnEscaping() + { + if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) { + return false; + } + + if ($this->lexer->isNextToken(EmailLexer::GENERIC)) { + throw new ExpectingATEXT(); + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) { + return false; + } + + $this->warnings[QuotedPart::CODE] = + new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']); + return true; + + } + + /** + * @param bool $hasClosingQuote + * + * @return bool + */ + protected function checkDQUOTE($hasClosingQuote) + { + if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) { + return $hasClosingQuote; + } + if ($hasClosingQuote) { + return $hasClosingQuote; + } + $previous = $this->lexer->getPrevious(); + if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) { + throw new ExpectingATEXT(); + } + + try { + $this->lexer->find(EmailLexer::S_DQUOTE); + $hasClosingQuote = true; + } catch (\Exception $e) { + throw new UnclosedQuotedString(); + } + $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']); + + return $hasClosingQuote; + } + + protected function checkCRLFInFWS() + { + if ($this->lexer->token['type'] !== EmailLexer::CRLF) { + return; + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { + throw new CRLFX2(); + } + + if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) { + throw new CRLFAtTheEnd(); + } + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php b/plugins/email/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php new file mode 100644 index 0000000..491082a --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/DNSCheckValidation.php @@ -0,0 +1,166 @@ +error = new LocalOrReservedDomain(); + return false; + } + + return $this->checkDns($host); + } + + public function getError() + { + return $this->error; + } + + public function getWarnings() + { + return $this->warnings; + } + + /** + * @param string $host + * + * @return bool + */ + protected function checkDns($host) + { + $variant = INTL_IDNA_VARIANT_UTS46; + + $host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; + + return $this->validateDnsRecords($host); + } + + + /** + * Validate the DNS records for given host. + * + * @param string $host A set of DNS records in the format returned by dns_get_record. + * + * @return bool True on success. + */ + private function validateDnsRecords($host) + { + // Get all MX, A and AAAA DNS records for host + // Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149 + $dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA); + + + // No MX, A or AAAA DNS records + if (empty($dnsRecords)) { + $this->error = new NoDNSRecord(); + return false; + } + + // For each DNS record + foreach ($dnsRecords as $dnsRecord) { + if (!$this->validateMXRecord($dnsRecord)) { + return false; + } + } + + // No MX records (fallback to A or AAAA records) + if (empty($this->mxRecords)) { + $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord(); + } + + return true; + } + + /** + * Validate an MX record + * + * @param array $dnsRecord Given DNS record. + * + * @return bool True if valid. + */ + private function validateMxRecord($dnsRecord) + { + if ($dnsRecord['type'] !== 'MX') { + return true; + } + + // "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505) + if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') { + $this->error = new DomainAcceptsNoMail(); + return false; + } + + $this->mxRecords[] = $dnsRecord; + + return true; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/EmailValidation.php b/plugins/email/vendor/egulias/email-validator/src/Validation/EmailValidation.php new file mode 100644 index 0000000..d5a015b --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/EmailValidation.php @@ -0,0 +1,34 @@ +errors = $errors; + parent::__construct(); + } + + /** + * @return InvalidEmail[] + */ + public function getErrors() + { + return $this->errors; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php b/plugins/email/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php new file mode 100644 index 0000000..feb2240 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php @@ -0,0 +1,124 @@ +validations = $validations; + $this->mode = $mode; + } + + /** + * {@inheritdoc} + */ + public function isValid($email, EmailLexer $emailLexer) + { + $result = true; + $errors = []; + foreach ($this->validations as $validation) { + $emailLexer->reset(); + $validationResult = $validation->isValid($email, $emailLexer); + $result = $result && $validationResult; + $this->warnings = array_merge($this->warnings, $validation->getWarnings()); + $errors = $this->addNewError($validation->getError(), $errors); + + if ($this->shouldStop($result)) { + break; + } + } + + if (!empty($errors)) { + $this->error = new MultipleErrors($errors); + } + + return $result; + } + + /** + * @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError + * @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors + * + * @return \Egulias\EmailValidator\Exception\InvalidEmail[] + */ + private function addNewError($possibleError, array $errors) + { + if (null !== $possibleError) { + $errors[] = $possibleError; + } + + return $errors; + } + + /** + * @param bool $result + * + * @return bool + */ + private function shouldStop($result) + { + return !$result && $this->mode === self::STOP_ON_ERROR; + } + + /** + * Returns the validation errors. + * + * @return MultipleErrors|null + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + */ + public function getWarnings() + { + return $this->warnings; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php b/plugins/email/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php new file mode 100644 index 0000000..6b31e54 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/NoRFCWarningsValidation.php @@ -0,0 +1,41 @@ +getWarnings())) { + return true; + } + + $this->error = new RFCWarnings(); + + return false; + } + + /** + * {@inheritdoc} + */ + public function getError() + { + return $this->error ?: parent::getError(); + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/RFCValidation.php b/plugins/email/vendor/egulias/email-validator/src/Validation/RFCValidation.php new file mode 100644 index 0000000..8781e0b --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/RFCValidation.php @@ -0,0 +1,49 @@ +parser = new EmailParser($emailLexer); + try { + $this->parser->parse((string)$email); + } catch (InvalidEmail $invalid) { + $this->error = $invalid; + return false; + } + + $this->warnings = $this->parser->getWarnings(); + return true; + } + + public function getError() + { + return $this->error; + } + + public function getWarnings() + { + return $this->warnings; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Validation/SpoofCheckValidation.php b/plugins/email/vendor/egulias/email-validator/src/Validation/SpoofCheckValidation.php new file mode 100644 index 0000000..e10bfab --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Validation/SpoofCheckValidation.php @@ -0,0 +1,51 @@ +setChecks(Spoofchecker::SINGLE_SCRIPT); + + if ($checker->isSuspicious($email)) { + $this->error = new SpoofEmail(); + } + + return $this->error === null; + } + + /** + * @return InvalidEmail|null + */ + public function getError() + { + return $this->error; + } + + public function getWarnings() + { + return []; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/AddressLiteral.php b/plugins/email/vendor/egulias/email-validator/src/Warning/AddressLiteral.php new file mode 100644 index 0000000..77e70f7 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/AddressLiteral.php @@ -0,0 +1,14 @@ +message = 'Address literal in domain part'; + $this->rfcNumber = 5321; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php b/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php new file mode 100644 index 0000000..be43bbe --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSNearAt.php @@ -0,0 +1,13 @@ +message = "Deprecated folding white space near @"; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php b/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php new file mode 100644 index 0000000..dea3450 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/CFWSWithFWS.php @@ -0,0 +1,13 @@ +message = 'Folding whites space followed by folding white space'; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/Comment.php b/plugins/email/vendor/egulias/email-validator/src/Warning/Comment.php new file mode 100644 index 0000000..704c290 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/Comment.php @@ -0,0 +1,13 @@ +message = "Comments found in this email"; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php b/plugins/email/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php new file mode 100644 index 0000000..ad43bd7 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/DeprecatedComment.php @@ -0,0 +1,13 @@ +message = 'Deprecated comments'; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/DomainLiteral.php b/plugins/email/vendor/egulias/email-validator/src/Warning/DomainLiteral.php new file mode 100644 index 0000000..6f36b5e --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/DomainLiteral.php @@ -0,0 +1,14 @@ +message = 'Domain Literal'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/DomainTooLong.php b/plugins/email/vendor/egulias/email-validator/src/Warning/DomainTooLong.php new file mode 100644 index 0000000..61ff17a --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/DomainTooLong.php @@ -0,0 +1,14 @@ +message = 'Domain is too long, exceeds 255 chars'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/EmailTooLong.php b/plugins/email/vendor/egulias/email-validator/src/Warning/EmailTooLong.php new file mode 100644 index 0000000..497309d --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/EmailTooLong.php @@ -0,0 +1,15 @@ +message = 'Email is too long, exceeds ' . EmailParser::EMAIL_MAX_LENGTH; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php new file mode 100644 index 0000000..ba2fcc0 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6BadChar.php @@ -0,0 +1,14 @@ +message = 'Bad char in IPV6 domain literal'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php new file mode 100644 index 0000000..41afa78 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonEnd.php @@ -0,0 +1,14 @@ +message = ':: found at the end of the domain literal'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php new file mode 100644 index 0000000..1bf754e --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6ColonStart.php @@ -0,0 +1,14 @@ +message = ':: found at the start of the domain literal'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php new file mode 100644 index 0000000..d752caa --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6Deprecated.php @@ -0,0 +1,14 @@ +message = 'Deprecated form of IPV6'; + $this->rfcNumber = 5321; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php new file mode 100644 index 0000000..4f82394 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6DoubleColon.php @@ -0,0 +1,14 @@ +message = 'Double colon found after IPV6 tag'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php new file mode 100644 index 0000000..a59d317 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6GroupCount.php @@ -0,0 +1,14 @@ +message = 'Group count is not IPV6 valid'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php new file mode 100644 index 0000000..936274c --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/IPV6MaxGroups.php @@ -0,0 +1,14 @@ +message = 'Reached the maximum number of IPV6 groups allowed'; + $this->rfcNumber = 5321; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/LabelTooLong.php b/plugins/email/vendor/egulias/email-validator/src/Warning/LabelTooLong.php new file mode 100644 index 0000000..daf07f4 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/LabelTooLong.php @@ -0,0 +1,14 @@ +message = 'Label too long'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/LocalTooLong.php b/plugins/email/vendor/egulias/email-validator/src/Warning/LocalTooLong.php new file mode 100644 index 0000000..0d08d8b --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/LocalTooLong.php @@ -0,0 +1,15 @@ +message = 'Local part is too long, exceeds 64 chars (octets)'; + $this->rfcNumber = 5322; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php b/plugins/email/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php new file mode 100644 index 0000000..b3c21a1 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/NoDNSMXRecord.php @@ -0,0 +1,14 @@ +message = 'No MX DSN record was found for this email'; + $this->rfcNumber = 5321; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php b/plugins/email/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php new file mode 100644 index 0000000..10f19e3 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/ObsoleteDTEXT.php @@ -0,0 +1,14 @@ +rfcNumber = 5322; + $this->message = 'Obsolete DTEXT in domain literal'; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedPart.php b/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedPart.php new file mode 100644 index 0000000..36a4265 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedPart.php @@ -0,0 +1,17 @@ +message = "Deprecated Quoted String found between $prevToken and $postToken"; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedString.php b/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedString.php new file mode 100644 index 0000000..817e4e8 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/QuotedString.php @@ -0,0 +1,17 @@ +message = "Quoted String found between $prevToken and $postToken"; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/TLD.php b/plugins/email/vendor/egulias/email-validator/src/Warning/TLD.php new file mode 100644 index 0000000..2338b9f --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/TLD.php @@ -0,0 +1,13 @@ +message = "RFC5321, TLD"; + } +} diff --git a/plugins/email/vendor/egulias/email-validator/src/Warning/Warning.php b/plugins/email/vendor/egulias/email-validator/src/Warning/Warning.php new file mode 100644 index 0000000..a2ee7b0 --- /dev/null +++ b/plugins/email/vendor/egulias/email-validator/src/Warning/Warning.php @@ -0,0 +1,47 @@ +message; + } + + /** + * @return int + */ + public function code() + { + return static::CODE; + } + + /** + * @return int + */ + public function RFCNumber() + { + return $this->rfcNumber; + } + + public function __toString() + { + return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.gitattributes b/plugins/email/vendor/swiftmailer/swiftmailer/.gitattributes new file mode 100644 index 0000000..dc96281 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.gitattributes @@ -0,0 +1,11 @@ +*.crt -crlf +*.key -crlf +*.srl -crlf +*.pub -crlf +*.priv -crlf +*.txt -crlf + +# ignore directories in the git-generated distributed .zip archive +/doc/notes export-ignore +/tests export-ignore +/phpunit.xml.dist export-ignore diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md b/plugins/email/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..5db6524 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ + + +| Q | A +| ------------------- | ----- +| Bug report? | yes/no +| Feature request? | yes/no +| RFC? | yes/no +| How used? | Standalone/Symfony/3party +| Swiftmailer version | x.y.z +| PHP version | x.y.z + +### Observed behaviour + + +### Expected behaviour + + +### Example + diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md b/plugins/email/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4b39510 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ + + +| Q | A +| ------------- | --- +| Bug fix? | yes/no +| New feature? | yes/no +| Doc update? | yes/no +| BC breaks? | yes/no +| Deprecations? | yes/no +| Fixed tickets | #... +| License | MIT + + + diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.gitignore b/plugins/email/vendor/swiftmailer/swiftmailer/.gitignore new file mode 100644 index 0000000..a911ddf --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.gitignore @@ -0,0 +1,9 @@ +/.php_cs.cache +/.phpunit +/.phpunit.result.cache +/build/* +/composer.lock +/phpunit.xml +/tests/acceptance.conf.php +/tests/smoke.conf.php +/vendor/ diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.php_cs.dist b/plugins/email/vendor/swiftmailer/swiftmailer/.php_cs.dist new file mode 100644 index 0000000..563b48b --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.php_cs.dist @@ -0,0 +1,21 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit75Migration:risky' => true, + 'php_unit_dedicate_assert' => ['target' => '5.6'], + 'array_syntax' => ['syntax' => 'short'], + 'php_unit_fqcn_annotation' => true, + 'no_unreachable_default_argument_value' => false, + 'braces' => ['allow_single_line_closure' => true], + 'heredoc_to_nowdoc' => false, + 'ordered_imports' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], + 'fopen_flags' => false, + ]) + ->setRiskyAllowed(true) + ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__)) +; diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml b/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml new file mode 100644 index 0000000..4468782 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml @@ -0,0 +1,30 @@ +language: php + +before_script: + - cp tests/acceptance.conf.php.default tests/acceptance.conf.php + - cp tests/smoke.conf.php.default tests/smoke.conf.php + - composer self-update + - composer update --no-interaction --prefer-source + - gem install mime-types -v 2.99.1 + - gem install mailcatcher + - mailcatcher --smtp-port 4456 + +script: ./vendor/bin/simple-phpunit + +env: + global: + - SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 + +matrix: + include: + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: 8.0 + fast_finish: true + +cache: + directories: + - .phpunit diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/CHANGES b/plugins/email/vendor/swiftmailer/swiftmailer/CHANGES new file mode 100644 index 0000000..5f80420 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/CHANGES @@ -0,0 +1,366 @@ +Changelog +========= + +6.2.5 (2021-XX-XX) +------------------ + + * n/a + +6.2.4 (2020-12-08) +------------------ + + * Prevent flushing of the bubble queue when event handler raises another event + * Add support for PHP 8 + * Code cleanups + +6.2.3 (2019-11-12) +------------------ + + * no changes + +6.2.2 (2019-11-12) +------------------ + + * fixed compat with PHP 7.4 + * fixed error message when connecting to a stream raises an error before connect() + +6.2.1 (2019-04-21) +------------------ + + * reverted "deprecated Swift_CharacterStream_ArrayCharacterStream and Swift_CharacterStream_NgCharacterStream in favor of Swift_CharacterStream_CharacterStream" + +6.2.0 (2019-03-10) +------------------ + + * added support for symfony/polyfill-intl-dn + * deprecated Swift_CharacterStream_ArrayCharacterStream and Swift_CharacterStream_NgCharacterStream in favor of Swift_CharacterStream_CharacterStream + +6.1.3 (2018-09-11) +------------------ + + * added auto-start to the SMTP transport when sending a message + * tweaked error message when the response from an SMTP server is empty + * fixed missing property in Swift_Mime_IdGenerator + * exposed original body content type with Swift_Mime_SimpleMimeEntity::getBodyContentType() + * fixed typo in variable name in Swift_AddressEncoder_IdnAddressEncoder + * fixed return type in MessageLogger + * fixed missing property addressEncoder in SimpleHeaderFactory class + +6.1.2 (2018-07-13) +------------------ + + * handled recipient errors when pipelining + +6.1.1 (2018-07-04) +------------------ + + * removed hard dependency on an IDN encoder + +6.1.0 (2018-07-02) +------------------ + + * added address encoder exceptions during send + * added support for bubbling up authenticator error messages + * added support for non-ASCII email addresses + * introduced new dependencies: transport.smtphandlers and transport.authhandlers + * deprecated Swift_Signers_OpenDKIMSigner; use Swift_Signers_DKIMSigner instead + * added support for SMTP pipelining + * added Swift_Transport_Esmtp_EightBitMimeHandler + * fixed startTLS only allowed tls1.0, now allowed: tls1.0, tls1.1, tls1.2 + +6.0.2 (2017-09-30) +------------------ + + * fixed DecoratorPlugin + * removed usage of getmypid() + +6.0.1 (2017-05-20) +------------------ + + * fixed BC break that can be avoided easily + +6.0.0 (2017-05-19) +------------------ + + * added Swift_Transport::ping() + * removed Swift_Mime_HeaderFactory, Swift_Mime_HeaderSet, Swift_Mime_Message, Swift_Mime_MimeEntity, + and Swift_Mime_ParameterizedHeader interfaces + * removed Swift_MailTransport and Swift_Transport_MailTransport + * removed Swift_Encoding + * removed the Swift_Transport_MailInvoker interface and Swift_Transport_SimpleMailInvoker class + * removed the Swift_SignedMessage class + * removed newInstance() methods everywhere + * methods operating on Date header now use DateTimeImmutable object instead of Unix timestamp; + Swift_Mime_Headers_DateHeader::getTimestamp()/setTimestamp() renamed to getDateTime()/setDateTime() + * bumped minimum version to PHP 7.0 + * removed Swift_Validate and replaced by egulias/email-validator + +5.4.9 (2018-01-23) +------------------ + + * no changes, last version of the 5.x series + +5.4.8 (2017-05-01) +------------------ + + * fixed encoding inheritance in addPart() + * fixed sorting MIME children when their types are equal + +5.4.7 (2017-04-20) +------------------ + + * fixed NTLMAuthenticator clobbering bcmath scale + +5.4.6 (2017-02-13) +------------------ + + * removed exceptions thrown in destructors as they lead to fatal errors + * switched to use sha256 by default in DKIM as per the RFC + * fixed an 'Undefined variable: pipes' PHP notice + * fixed long To headers when using the mail transport + * fixed NTLMAuthenticator when no domain is passed with the username + * prevented fatal error during unserialization of a message + * fixed a PHP warning when sending a message that has a length of a multiple of 8192 + +5.4.5 (2016-12-29) +------------------ + + * SECURITY FIX: fixed CVE-2016-10074 by disallowing potentially unsafe shell characters + + Prior to 5.4.5, the mail transport (Swift_Transport_MailTransport) was vulnerable to passing + arbitrary shell arguments if the "From", "ReturnPath" or "Sender" header came + from a non-trusted source, potentially allowing Remote Code Execution + * deprecated the mail transport + +5.4.4 (2016-11-23) +------------------ + + * reverted escaping command-line args to mail (PHP mail() function already does it) + +5.4.3 (2016-07-08) +------------------ + + * fixed SimpleHeaderSet::has()/get() when the 0 index is removed + * removed the need to have mcrypt installed + * fixed broken MIME header encoding with quotes/colons and non-ascii chars + * allowed mail transport send for messages without To header + * fixed PHP 7 support + +5.4.2 (2016-05-01) +------------------ + + * fixed support for IPv6 sockets + * added auto-retry when sending messages from the memory spool + * fixed consecutive read calls in Swift_ByteStream_FileByteStream + * added support for iso-8859-15 encoding + * fixed PHP mail extra params on missing reversePath + * added methods to set custom stream context options + * fixed charset changes in QpContentEncoderProxy + * added return-path header to the ignoredHeaders list of DKIMSigner + * fixed crlf for subject using mail + * fixed add soft line break only when necessary + * fixed escaping command-line args to mail + +5.4.1 (2015-06-06) +------------------ + + * made Swiftmailer exceptions confirm to PHP base exception constructor signature + * fixed MAIL FROM & RCPT TO headers to be RFC compliant + +5.4.0 (2015-03-14) +------------------ + + * added the possibility to add extra certs to PKCS#7 signature + * fix base64 encoding with streams + * added a new RESULT_SPOOLED status for SpoolTransport + * fixed getBody() on attachments when called more than once + * removed dots from generated filenames in filespool + +5.3.1 (2014-12-05) +------------------ + + * fixed cloning of messages with attachments + +5.3.0 (2014-10-04) +------------------ + + * fixed cloning when using signers + * reverted removal of Swift_Encoding + * drop support for PHP 5.2.x + +5.2.2 (2014-09-20) +------------------ + + * fixed Japanese support + * fixed the memory spool when the message changes when in the pool + * added support for cloning messages + * fixed PHP warning in the redirect plugin + * changed the way to and cc-ed email are sent to only use one transaction + +5.2.1 (2014-06-13) +------------------ + + * SECURITY FIX: fixed CLI escaping when using sendmail as a transport + + Prior to 5.2.1, the sendmail transport (Swift_Transport_SendmailTransport) + was vulnerable to an arbitrary shell execution if the "From" header came + from a non-trusted source and no "Return-Path" is configured. + + * fixed parameter in DKIMSigner + * fixed compatibility with PHP < 5.4 + +5.2.0 (2014-05-08) +------------------ + + * fixed Swift_ByteStream_FileByteStream::read() to match to the specification + * fixed from-charset and to-charset arguments in mbstring_convert_encoding() usages + * fixed infinite loop in StreamBuffer + * fixed NullTransport to return the number of ignored emails instead of 0 + * Use phpunit and mockery for unit testing (realityking) + +5.1.0 (2014-03-18) +------------------ + + * fixed data writing to stream when sending large messages + * added support for libopendkim (https://github.com/xdecock/php-opendkim) + * merged SignedMessage and Message + * added Gmail XOAuth2 authentication + * updated the list of known mime types + * added NTLM authentication + +5.0.3 (2013-12-03) +------------------ + + * fixed double-dot bug + * fixed DKIM signer + +5.0.2 (2013-08-30) +------------------ + + * handled correct exception type while reading IoBuffer output + +5.0.1 (2013-06-17) +------------------ + + * changed the spool to only start the transport when a mail has to be sent + * fixed compatibility with PHP 5.2 + * fixed LICENSE file + +5.0.0 (2013-04-30) +------------------ + + * changed the license from LGPL to MIT + +4.3.1 (2013-04-11) +------------------ + + * removed usage of the native QP encoder when the charset is not UTF-8 + * fixed usage of uniqid to avoid collisions + * made a performance improvement when tokenizing large headers + * fixed usage of the PHP native QP encoder on PHP 5.4.7+ + +4.3.0 (2013-01-08) +------------------ + + * made the temporary directory configurable via the TMPDIR env variable + * added S/MIME signer and encryption support + +4.2.2 (2012-10-25) +------------------ + + * added the possibility to throttle messages per second in ThrottlerPlugin (mostly for Amazon SES) + * switched mime.qpcontentencoder to automatically use the PHP native encoder on PHP 5.4.7+ + * allowed specifying a whitelist with regular expressions in RedirectingPlugin + +4.2.1 (2012-07-13) +------------------ + + * changed the coding standards to PSR-1/2 + * fixed issue with autoloading + * added NativeQpContentEncoder to enhance performance (for PHP 5.3+) + +4.2.0 (2012-06-29) +------------------ + + * added documentation about how to use the Japanese support introduced in 4.1.8 + * added a way to override the default configuration in a lazy way + * changed the PEAR init script to lazy-load the initialization + * fixed a bug when calling Swift_Preferences before anything else (regression introduced in 4.1.8) + +4.1.8 (2012-06-17) +------------------ + + * added Japanese iso-2022-jp support + * changed the init script to lazy-load the initialization + * fixed docblocks (@id) which caused some problems with libraries parsing the dobclocks + * fixed Swift_Mime_Headers_IdentificationHeader::setId() when passed an array of ids + * fixed encoding of email addresses in headers + * added replacements setter to the Decorator plugin + +4.1.7 (2012-04-26) +------------------ + + * fixed QpEncoder safeMapShareId property + +4.1.6 (2012-03-23) +------------------ + + * reduced the size of serialized Messages + +4.1.5 (2012-01-04) +------------------ + + * enforced Swift_Spool::queueMessage() to return a Boolean + * made an optimization to the memory spool: start the transport only when required + * prevented stream_socket_client() from generating an error and throw a Swift_TransportException instead + * fixed a PHP warning when calling to mail() when safe_mode is off + * many doc tweaks + +4.1.4 (2011-12-16) +------------------ + + * added a memory spool (Swift_MemorySpool) + * fixed too many opened files when sending emails with attachments + +4.1.3 (2011-10-27) +------------------ + + * added STARTTLS support + * added missing @return tags on fluent methods + * added a MessageLogger plugin that logs all sent messages + * added composer.json + +4.1.2 (2011-09-13) +------------------ + + * fixed wrong detection of magic_quotes_runtime + * fixed fatal errors when no To or Subject header has been set + * fixed charset on parameter header continuations + * added documentation about how to install Swiftmailer from the PEAR channel + * fixed various typos and markup problem in the documentation + * fixed warning when cache directory does not exist + * fixed "slashes are escaped" bug + * changed require_once() to require() in autoload + +4.1.1 (2011-07-04) +------------------ + + * added missing file in PEAR package + +4.1.0 (2011-06-30) +------------------ + + * documentation has been converted to ReST + +4.1.0 RC1 (2011-06-17) +---------------------- + +New features: + + * changed the Decorator Plugin to allow replacements in all headers + * added Swift_Mime_Grammar and Swift_Validate to validate an email address + * modified the autoloader to lazy-initialize Swiftmailer + * removed Swift_Mailer::batchSend() + * added NullTransport + * added new plugins: RedirectingPlugin and ImpersonatePlugin + * added a way to send messages asynchronously (Spool) diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/LICENSE b/plugins/email/vendor/swiftmailer/swiftmailer/LICENSE new file mode 100644 index 0000000..ab72077 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2021 Fabien Potencier + +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/plugins/email/vendor/swiftmailer/swiftmailer/README.md b/plugins/email/vendor/swiftmailer/swiftmailer/README.md new file mode 100644 index 0000000..0e1fb4c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/README.md @@ -0,0 +1,19 @@ +Swift Mailer +------------ + +Swift Mailer is a component based mailing solution for PHP. +It is released under the MIT license. + +Swift Mailer is highly object-oriented by design and lends itself +to use in complex web application with a great deal of flexibility. + +For full details on usage, read the [documentation](https://swiftmailer.symfony.com/docs/introduction.html). + +Sponsors +-------- + + diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/composer.json b/plugins/email/vendor/swiftmailer/swiftmailer/composer.json new file mode 100644 index 0000000..d66dc4c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/composer.json @@ -0,0 +1,42 @@ +{ + "name": "swiftmailer/swiftmailer", + "type": "library", + "description": "Swiftmailer, free feature-rich PHP mailer", + "keywords": ["mail","mailer","email"], + "homepage": "https://swiftmailer.symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=7.0.0", + "egulias/email-validator": "^2.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "symfony/phpunit-bridge": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses" + }, + "autoload": { + "files": ["lib/swift_required.php"] + }, + "autoload-dev": { + "psr-0": { "Swift_": "tests/unit" } + }, + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/headers.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/headers.rst new file mode 100644 index 0000000..8b8bece --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/headers.rst @@ -0,0 +1,621 @@ +Message Headers +=============== + +Sometimes you'll want to add your own headers to a message or modify/remove +headers that are already present. You work with the message's HeaderSet to do +this. + +Header Basics +------------- + +All MIME entities in Swift Mailer -- including the message itself -- store +their headers in a single object called a HeaderSet. This HeaderSet is +retrieved with the ``getHeaders()`` method. + +As mentioned in the previous chapter, everything that forms a part of a message +in Swift Mailer is a MIME entity that is represented by an instance of +``Swift_Mime_SimpleMimeEntity``. This includes -- most notably -- the message +object itself, attachments, MIME parts and embedded images. Each of these MIME +entities consists of a body and a set of headers that describe the body. + +For all of the "standard" headers in these MIME entities, such as the +``Content-Type``, there are named methods for working with them, such as +``setContentType()`` and ``getContentType()``. This is because headers are a +moderately complex area of the library. Each header has a slightly different +required structure that it must meet in order to comply with the standards that +govern email (and that are checked by spam blockers etc). + +You fetch the HeaderSet from a MIME entity like so:: + + $message = new Swift_Message(); + + // Fetch the HeaderSet from a Message object + $headers = $message->getHeaders(); + + $attachment = Swift_Attachment::fromPath('document.pdf'); + + // Fetch the HeaderSet from an attachment object + $headers = $attachment->getHeaders(); + +The job of the HeaderSet is to contain and manage instances of Header objects. +Depending upon the MIME entity the HeaderSet came from, the contents of the +HeaderSet will be different, since an attachment for example has a different +set of headers to those in a message. + +You can find out what the HeaderSet contains with a quick loop, dumping out the +names of the headers:: + + foreach ($headers->getAll() as $header) { + printf("%s
    \n", $header->getFieldName()); + } + + /* + Content-Transfer-Encoding + Content-Type + MIME-Version + Date + Message-ID + From + Subject + To + */ + +You can also dump out the rendered HeaderSet by calling its ``toString()`` +method:: + + echo $headers->toString(); + + /* + Message-ID: <1234869991.499a9ee7f1d5e@swift.generated> + Date: Tue, 17 Feb 2009 22:26:31 +1100 + Subject: Awesome subject! + From: sender@example.org + To: recipient@example.org + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + */ + +Where the complexity comes in is when you want to modify an existing header. +This complexity comes from the fact that each header can be of a slightly +different type (such as a Date header, or a header that contains email +addresses, or a header that has key-value parameters on it!). Each header in +the HeaderSet is an instance of ``Swift_Mime_Header``. They all have common +functionality, but knowing exactly what type of header you're working with will +allow you a little more control. + +You can determine the type of header by comparing the return value of its +``getFieldType()`` method with the constants ``TYPE_TEXT``, +``TYPE_PARAMETERIZED``, ``TYPE_DATE``, ``TYPE_MAILBOX``, ``TYPE_ID`` and +``TYPE_PATH`` which are defined in ``Swift_Mime_Header``:: + + foreach ($headers->getAll() as $header) { + switch ($header->getFieldType()) { + case Swift_Mime_Header::TYPE_TEXT: $type = 'text'; + break; + case Swift_Mime_Header::TYPE_PARAMETERIZED: $type = 'parameterized'; + break; + case Swift_Mime_Header::TYPE_MAILBOX: $type = 'mailbox'; + break; + case Swift_Mime_Header::TYPE_DATE: $type = 'date'; + break; + case Swift_Mime_Header::TYPE_ID: $type = 'ID'; + break; + case Swift_Mime_Header::TYPE_PATH: $type = 'path'; + break; + } + printf("%s: is a %s header
    \n", $header->getFieldName(), $type); + } + + /* + Content-Transfer-Encoding: is a text header + Content-Type: is a parameterized header + MIME-Version: is a text header + Date: is a date header + Message-ID: is a ID header + From: is a mailbox header + Subject: is a text header + To: is a mailbox header + */ + +Headers can be removed from the set, modified within the set, or added to the +set. + +The following sections show you how to work with the HeaderSet and explain the +details of each implementation of ``Swift_Mime_Header`` that may exist within +the HeaderSet. + +Header Types +------------ + +Because all headers are modeled on different data (dates, addresses, text!) +there are different types of Header in Swift Mailer. Swift Mailer attempts to +categorize all possible MIME headers into more general groups, defined by a +small number of classes. + +Text Headers +~~~~~~~~~~~~ + +Text headers are the simplest type of Header. They contain textual information +with no special information included within it -- for example the Subject +header in a message. + +There's nothing particularly interesting about a text header, though it is +probably the one you'd opt to use if you need to add a custom header to a +message. It represents text just like you'd think it does. If the text contains +characters that are not permitted in a message header (such as new lines, or +non-ascii characters) then the header takes care of encoding the text so that +it can be used. + +No header -- including text headers -- in Swift Mailer is vulnerable to +header-injection attacks. Swift Mailer breaks any attempt at header injection +by encoding the dangerous data into a non-dangerous form. + +It's easy to add a new text header to a HeaderSet. You do this by calling the +HeaderSet's ``addTextHeader()`` method:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addTextHeader('Your-Header-Name', 'the header value'); + +Changing the value of an existing text header is done by calling it's +``setValue()`` method:: + + $subject = $message->getHeaders()->get('Subject'); + $subject->setValue('new subject'); + +When output via ``toString()``, a text header produces something like the +following:: + + $subject = $message->getHeaders()->get('Subject'); + $subject->setValue('amazing subject line'); + echo $subject->toString(); + + /* + + Subject: amazing subject line + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded. This is nothing to be concerned about since mail +clients will decode them back:: + + $subject = $message->getHeaders()->get('Subject'); + $subject->setValue('contains – dash'); + echo $subject->toString(); + + /* + + Subject: contains =?utf-8?Q?=E2=80=93?= dash + + */ + +Parameterized Headers +~~~~~~~~~~~~~~~~~~~~~ + +Parameterized headers are text headers that contain key-value parameters +following the textual content. The Content-Type header of a message is a +parameterized header since it contains charset information after the content +type. + +The parameterized header type is a special type of text header. It extends the +text header by allowing additional information to follow it. All of the methods +from text headers are available in addition to the methods described here. + +Adding a parameterized header to a HeaderSet is done by using the +``addParameterizedHeader()`` method which takes a text value like +``addTextHeader()`` but it also accepts an associative array of key-value +parameters:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addParameterizedHeader( + 'Header-Name', 'header value', + ['foo' => 'bar'] + ); + +To change the text value of the header, call it's ``setValue()`` method just as +you do with text headers. + +To change the parameters in the header, call the header's ``setParameters()`` +method or the ``setParameter()`` method (note the pluralization):: + + $type = $message->getHeaders()->get('Content-Type'); + + // setParameters() takes an associative array + $type->setParameters([ + 'name' => 'file.txt', + 'charset' => 'iso-8859-1' + ]); + + // setParameter() takes two args for $key and $value + $type->setParameter('charset', 'iso-8859-1'); + +When output via ``toString()``, a parameterized header produces something like +the following:: + + $type = $message->getHeaders()->get('Content-Type'); + $type->setValue('text/html'); + $type->setParameter('charset', 'utf-8'); + + echo $type->toString(); + + /* + + Content-Type: text/html; charset=utf-8 + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded, just like they are for text headers. This is +nothing to be concerned about since mail clients will decode them back. +Likewise, if the parameters contain any non-ascii characters they will be +encoded so that they can be transmitted safely:: + + $attachment = new Swift_Attachment(); + $disp = $attachment->getHeaders()->get('Content-Disposition'); + $disp->setValue('attachment'); + $disp->setParameter('filename', 'report–may.pdf'); + echo $disp->toString(); + + /* + + Content-Disposition: attachment; filename*=utf-8''report%E2%80%93may.pdf + + */ + +Date Headers +~~~~~~~~~~~~ + +Date headers contains an RFC 2822 formatted date (i.e. what PHP's ``date('r')`` +returns). They are used anywhere a date or time is needed to be presented as a +message header. + +The data on which a date header is modeled as a DateTimeImmutable object. The +object is used to create a correctly structured RFC 2822 formatted date with +timezone such as ``Tue, 17 Feb 2009 22:26:31 +1100``. + +The obvious place this header type is used is in the ``Date:`` header of the +message itself. + +It's easy to add a new date header to a HeaderSet. You do this by calling the +HeaderSet's ``addDateHeader()`` method:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addDateHeader('Your-Header', new DateTimeImmutable('3 days ago')); + +Changing the value of an existing date header is done by calling it's +``setDateTime()`` method:: + + $date = $message->getHeaders()->get('Date'); + $date->setDateTime(new DateTimeImmutable()); + +When output via ``toString()``, a date header produces something like the +following:: + + $date = $message->getHeaders()->get('Date'); + echo $date->toString(); + + /* + + Date: Wed, 18 Feb 2009 13:35:02 +1100 + + */ + +Mailbox (e-mail address) Headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mailbox headers contain one or more email addresses, possibly with personalized +names attached to them. The data on which they are modeled is represented by an +associative array of email addresses and names. + +Mailbox headers are probably the most complex header type to understand in +Swift Mailer because they accept their input as an array which can take various +forms, as described in the previous chapter. + +All of the headers that contain e-mail addresses in a message -- with the +exception of ``Return-Path:`` which has a stricter syntax -- use this header +type. That is, ``To:``, ``From:`` etc. + +You add a new mailbox header to a HeaderSet by calling the HeaderSet's +``addMailboxHeader()`` method:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addMailboxHeader('Your-Header-Name', [ + 'person1@example.org' => 'Person Name One', + 'person2@example.org', + 'person3@example.org', + 'person4@example.org' => 'Another named person' + ]); + +Changing the value of an existing mailbox header is done by calling it's +``setNameAddresses()`` method:: + + $to = $message->getHeaders()->get('To'); + $to->setNameAddresses([ + 'joe@example.org' => 'Joe Bloggs', + 'john@example.org' => 'John Doe', + 'no-name@example.org' + ]); + +If you don't wish to concern yourself with the complicated accepted input +formats accepted by ``setNameAddresses()`` as described in the previous chapter +and you only want to set one or more addresses (not names) then you can just +use the ``setAddresses()`` method instead:: + + $to = $message->getHeaders()->get('To'); + $to->setAddresses([ + 'joe@example.org', + 'john@example.org', + 'no-name@example.org' + ]); + +.. note:: + + Both methods will accept the above input format in practice. + +If all you want to do is set a single address in the header, you can use a +string as the input parameter to ``setAddresses()`` and/or +``setNameAddresses()``:: + + $to = $message->getHeaders()->get('To'); + $to->setAddresses('joe-bloggs@example.org'); + +When output via ``toString()``, a mailbox header produces something like the +following:: + + $to = $message->getHeaders()->get('To'); + $to->setNameAddresses([ + 'person1@example.org' => 'Name of Person', + 'person2@example.org', + 'person3@example.org' => 'Another Person' + ]); + + echo $to->toString(); + + /* + + To: Name of Person , person2@example.org, Another Person + + + */ + +Internationalized domains are automatically converted to IDN encoding:: + + $to = $message->getHeaders()->get('To'); + $to->setAddresses('joe@ëxämple.org'); + + echo $to->toString(); + + /* + + To: joe@xn--xmple-gra1c.org + + */ + +ID Headers +~~~~~~~~~~ + +ID headers contain identifiers for the entity (or the message). The most +notable ID header is the Message-ID header on the message itself. + +An ID that exists inside an ID header looks more-or-less less like an email +address. For example, ``<1234955437.499becad62ec2@example.org>``. The part to +the left of the @ sign is usually unique, based on the current time and some +random factor. The part on the right is usually a domain name. + +Any ID passed to the header's ``setId()`` method absolutely MUST conform to +this structure, otherwise you'll get an Exception thrown at you by Swift Mailer +(a ``Swift_RfcComplianceException``). This is to ensure that the generated +email complies with relevant RFC documents and therefore is less likely to be +blocked as spam. + +It's easy to add a new ID header to a HeaderSet. You do this by calling the +HeaderSet's ``addIdHeader()`` method:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addIdHeader('Your-Header-Name', '123456.unqiue@example.org'); + +Changing the value of an existing ID header is done by calling its ``setId()`` +method:: + + $msgId = $message->getHeaders()->get('Message-ID'); + $msgId->setId(time() . '.' . uniqid('thing') . '@example.org'); + +When output via ``toString()``, an ID header produces something like the +following:: + + $msgId = $message->getHeaders()->get('Message-ID'); + echo $msgId->toString(); + + /* + + Message-ID: <1234955437.499becad62ec2@example.org> + + */ + +Path Headers +~~~~~~~~~~~~ + +Path headers are like very-restricted mailbox headers. They contain a single +email address with no associated name. The Return-Path header of a message is a +path header. + +You add a new path header to a HeaderSet by calling the HeaderSet's +``addPathHeader()`` method:: + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addPathHeader('Your-Header-Name', 'person@example.org'); + +Changing the value of an existing path header is done by calling its +``setAddress()`` method:: + + $return = $message->getHeaders()->get('Return-Path'); + $return->setAddress('my-address@example.org'); + +When output via ``toString()``, a path header produces something like the +following:: + + $return = $message->getHeaders()->get('Return-Path'); + $return->setAddress('person@example.org'); + echo $return->toString(); + + /* + + Return-Path: + + */ + +Header Operations +----------------- + +Working with the headers in a message involves knowing how to use the methods +on the HeaderSet and on the individual Headers within the HeaderSet. + +Adding new Headers +~~~~~~~~~~~~~~~~~~ + +New headers can be added to the HeaderSet by using one of the provided +``add..Header()`` methods. + +The added header will appear in the message when it is sent:: + + // Adding a custom header to a message + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addTextHeader('X-Mine', 'something here'); + + // Adding a custom header to an attachment + $attachment = Swift_Attachment::fromPath('/path/to/doc.pdf'); + $attachment->getHeaders()->addDateHeader('X-Created-Time', time()); + +Retrieving Headers +~~~~~~~~~~~~~~~~~~ + +Headers are retrieved through the HeaderSet's ``get()`` and ``getAll()`` +methods:: + + $headers = $message->getHeaders(); + + // Get the To: header + $toHeader = $headers->get('To'); + + // Get all headers named "X-Foo" + $fooHeaders = $headers->getAll('X-Foo'); + + // Get the second header named "X-Foo" + $foo = $headers->get('X-Foo', 1); + + // Get all headers that are present + $all = $headers->getAll(); + +When using ``get()`` a single header is returned that matches the name (case +insensitive) that is passed to it. When using ``getAll()`` with a header name, +an array of headers with that name are returned. Calling ``getAll()`` with no +arguments returns an array of all headers present in the entity. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``getAll()`` exists to fetch all + headers with a specified name. In addition, ``get()`` accepts an optional + numerical index, starting from zero to specify which header you want more + specifically. + +.. note:: + + If you want to modify the contents of the header and you don't know for + sure what type of header it is then you may need to check the type by + calling its ``getFieldType()`` method. + +Check if a Header Exists +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can check if a named header is present in a HeaderSet by calling its +``has()`` method:: + + $headers = $message->getHeaders(); + + // Check if the To: header exists + if ($headers->has('To')) { + echo 'To: exists'; + } + + // Check if an X-Foo header exists twice (i.e. check for the 2nd one) + if ($headers->has('X-Foo', 1)) { + echo 'Second X-Foo header exists'; + } + +If the header exists, ``true`` will be returned or ``false`` if not. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``has()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. + +Removing Headers +~~~~~~~~~~~~~~~~ + +Removing a Header from the HeaderSet is done by calling the HeaderSet's +``remove()`` or ``removeAll()`` methods:: + + $headers = $message->getHeaders(); + + // Remove the Subject: header + $headers->remove('Subject'); + + // Remove all X-Foo headers + $headers->removeAll('X-Foo'); + + // Remove only the second X-Foo header + $headers->remove('X-Foo', 1); + +When calling ``remove()`` a single header will be removed. When calling +``removeAll()`` all headers with the given name will be removed. If no headers +exist with the given name, no errors will occur. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``remove()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. For the same reason, ``removeAll()`` exists to + remove all headers that have the given name. + +Modifying a Header's Content +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To change a Header's content you should know what type of header it is and then +call it's appropriate setter method. All headers also have a +``setFieldBodyModel()`` method that accepts a mixed parameter and delegates to +the correct setter:: + +The header will be updated inside the HeaderSet and the changes will be seen +when the message is sent:: + + $headers = $message->getHeaders(); + + // Change the Subject: header + $subj = $headers->get('Subject'); + $subj->setValue('new subject here'); + + // Change the To: header + $to = $headers->get('To'); + $to->setNameAddresses([ + 'person@example.org' => 'Person', + 'thing@example.org' + ]); + + // Using the setFieldBodyModel() just delegates to the correct method + // So here to calls setNameAddresses() + $to->setFieldBodyModel([ + 'person@example.org' => 'Person', + 'thing@example.org' + ]); diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/index.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/index.rst new file mode 100644 index 0000000..5d92889 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/index.rst @@ -0,0 +1,12 @@ +Swiftmailer +=========== + +.. toctree:: + :maxdepth: 2 + + introduction + messages + headers + sending + plugins + japanese diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/introduction.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/introduction.rst new file mode 100644 index 0000000..8c61cfb --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/introduction.rst @@ -0,0 +1,61 @@ +Introduction +============ + +Swift Mailer is a component based library for sending e-mails from PHP applications. + +System Requirements +------------------- + +Swift Mailer requires PHP 7.0 or higher (``proc_*`` functions must be +available). + +Swift Mailer does not work when used with function overloading as implemented +by ``mbstring`` when ``mbstring.func_overload`` is set to ``2``. + +Installation +------------ + +The recommended way to install Swiftmailer is via Composer: + +.. code-block:: bash + + $ composer require "swiftmailer/swiftmailer:^6.0" + +Basic Usage +----------- + +Here is the simplest way to send emails with Swift Mailer:: + + require_once '/path/to/vendor/autoload.php'; + + // Create the Transport + $transport = (new Swift_SmtpTransport('smtp.example.org', 25)) + ->setUsername('your username') + ->setPassword('your password') + ; + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + + // Create a message + $message = (new Swift_Message('Wonderful Subject')) + ->setFrom(['john@doe.com' => 'John Doe']) + ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']) + ->setBody('Here is the message itself') + ; + + // Send the message + $result = $mailer->send($message); + +You can also use Sendmail as a transport:: + + // Sendmail + $transport = new Swift_SendmailTransport('/usr/sbin/sendmail -bs'); + +Getting Help +------------ + +For general support, use `Stack Overflow `_. + +For bug reports and feature requests, create a new ticket in `GitHub +`_. diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/japanese.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/japanese.rst new file mode 100644 index 0000000..5454821 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/japanese.rst @@ -0,0 +1,19 @@ +Using Swift Mailer for Japanese Emails +====================================== + +To send emails in Japanese, you need to tweak the default configuration. + +Call the ``Swift::init()`` method with the following code as early as possible +in your code:: + + Swift::init(function () { + Swift_DependencyContainer::getInstance() + ->register('mime.qpheaderencoder') + ->asAliasOf('mime.base64headerencoder'); + + Swift_Preferences::getInstance()->setCharset('iso-2022-jp'); + }); + + /* rest of code goes here */ + +That's all! diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/messages.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/messages.rst new file mode 100644 index 0000000..53c5b36 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/messages.rst @@ -0,0 +1,947 @@ +Creating Messages +================= + +Creating messages in Swift Mailer is done by making use of the various MIME +entities provided with the library. Complex messages can be quickly created +with very little effort. + +Quick Reference +--------------- + +You can think of creating a Message as being similar to the steps you perform +when you click the Compose button in your mail client. You give it a subject, +specify some recipients, add any attachments and write your message:: + + // Create the message + $message = (new Swift_Message()) + + // Give the message a subject + ->setSubject('Your subject') + + // Set the From address with an associative array + ->setFrom(['john@doe.com' => 'John Doe']) + + // Set the To addresses with an associative array (setTo/setCc/setBcc) + ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']) + + // Give it a body + ->setBody('Here is the message itself') + + // And optionally an alternative body + ->addPart('Here is the message itself', 'text/html') + + // Optionally add any attachments + ->attach(Swift_Attachment::fromPath('my-document.pdf')) + ; + +Message Basics +-------------- + +A message is a container for anything you want to send to somebody else. There +are several basic aspects of a message that you should know. + +An e-mail message is made up of several relatively simple entities that are +combined in different ways to achieve different results. All of these entities +have the same fundamental outline but serve a different purpose. The Message +itself can be defined as a MIME entity, an Attachment is a MIME entity, all +MIME parts are MIME entities -- and so on! + +The basic units of each MIME entity -- be it the Message itself, or an +Attachment -- are its Headers and its body: + +.. code-block:: text + + Header-Name: A header value + Other-Header: Another value + + The body content itself + +The Headers of a MIME entity, and its body must conform to some strict +standards defined by various RFC documents. Swift Mailer ensures that these +specifications are followed by using various types of object, including +Encoders and different Header types to generate the entity. + +The Structure of a Message +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Of all of the MIME entities, a message -- ``Swift_Message`` is the largest and +most complex. It has many properties that can be updated and it can contain +other MIME entities -- attachments for example -- nested inside it. + +A Message has a lot of different Headers which are there to present information +about the message to the recipients' mail client. Most of these headers will be +familiar to the majority of users, but we'll list the basic ones. Although it's +possible to work directly with the Headers of a Message (or other MIME entity), +the standard Headers have accessor methods provided to abstract away the +complex details for you. For example, although the Date on a message is written +with a strict format, you only need to pass a DateTimeInterface instance to +``setDate()``. + ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| Header | Description | Accessors | ++===============================+====================================================================================================================================+=============================================+ +| ``Message-ID`` | Identifies this message with a unique ID, usually containing the domain name and time generated | ``getId()`` / ``setId()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Return-Path`` | Specifies where bounces should go (Swift Mailer reads this for other uses) | ``getReturnPath()`` / ``setReturnPath()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``From`` | Specifies the address of the person who the message is from. This can be multiple addresses if multiple people wrote the message. | ``getFrom()`` / ``setFrom()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Sender`` | Specifies the address of the person who physically sent the message (higher precedence than ``From:``) | ``getSender()`` / ``setSender()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``To`` | Specifies the addresses of the intended recipients | ``getTo()`` / ``setTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Cc`` | Specifies the addresses of recipients who will be copied in on the message | ``getCc()`` / ``setCc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Bcc`` | Specifies the addresses of recipients who the message will be blind-copied to. Other recipients will not be aware of these copies. | ``getBcc()`` / ``setBcc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Reply-To`` | Specifies the address where replies are sent to | ``getReplyTo()`` / ``setReplyTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Subject`` | Specifies the subject line that is displayed in the recipients' mail client | ``getSubject()`` / ``setSubject()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Date`` | Specifies the date at which the message was sent | ``getDate()`` / ``setDate()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Type`` | Specifies the format of the message (usually ``text/plain`` or ``text/html``) | ``getContentType()`` / ``setContentType()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Transfer-Encoding`` | Specifies the encoding scheme in the message | ``getEncoder()`` / ``setEncoder()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ + +Working with a Message Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Although there are a lot of available methods on a message object, you only +need to make use of a small subset of them. Usually you'll use +``setSubject()``, ``setTo()`` and ``setFrom()`` before setting the body of your +message with ``setBody()``:: + + $message = new Swift_Message(); + $message->setSubject('My subject'); + +All MIME entities (including a message) have a ``toString()`` method that you +can call if you want to take a look at what is going to be sent. For example, +if you ``echo $message->toString();`` you would see something like this: + +.. code-block:: text + + Message-ID: <1230173678.4952f5eeb1432@swift.generated> + Date: Thu, 25 Dec 2008 13:54:38 +1100 + Subject: Example subject + From: Chris Corbyn + To: Receiver Name + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + + Here is the message + +We'll take a closer look at the methods you use to create your message in the +following sections. + +Adding Content to Your Message +------------------------------ + +Rich content can be added to messages in Swift Mailer with relative ease by +calling methods such as ``setSubject()``, ``setBody()``, ``addPart()`` and +``attach()``. + +Setting the Subject Line +~~~~~~~~~~~~~~~~~~~~~~~~ + +The subject line, displayed in the recipients' mail client can be set with the +``setSubject()`` method, or as a parameter to ``new Swift_Message()``:: + + // Pass it as a parameter when you create the message + $message = new Swift_Message('My amazing subject'); + + // Or set it after like this + $message->setSubject('My amazing subject'); + +Setting the Body Content +~~~~~~~~~~~~~~~~~~~~~~~~ + +The body of the message -- seen when the user opens the message -- is specified +by calling the ``setBody()`` method. If an alternative body is to be included, +``addPart()`` can be used. + +The body of a message is the main part that is read by the user. Often people +want to send a message in HTML format (``text/html``), other times people want +to send in plain text (``text/plain``), or sometimes people want to send both +versions and allow the recipient to choose how they view the message. + +As a rule of thumb, if you're going to send a HTML email, always include a +plain-text equivalent of the same content so that users who prefer to read +plain text can do so. + +If the recipient's mail client offers preferences for displaying text vs. HTML +then the mail client will present that part to the user where available. In +other cases the mail client will display the "best" part it can - usually HTML +if you've included HTML:: + + // Pass it as a parameter when you create the message + $message = new Swift_Message('Subject here', 'My amazing body'); + + // Or set it after like this + $message->setBody('My amazing body', 'text/html'); + + // Add alternative parts with addPart() + $message->addPart('My amazing body in plain text', 'text/plain'); + +Attaching Files +--------------- + +Attachments are downloadable parts of a message and can be added by calling the +``attach()`` method on the message. You can add attachments that exist on disk, +or you can create attachments on-the-fly. + +Although we refer to files sent over e-mails as "attachments" -- because +they're attached to the message -- lots of other parts of the message are +actually "attached" even if we don't refer to these parts as attachments. + +File attachments are created by the ``Swift_Attachment`` class and then +attached to the message via the ``attach()`` method on it. For all of the +"every day" MIME types such as all image formats, word documents, PDFs and +spreadsheets you don't need to explicitly set the content-type of the +attachment, though it would do no harm to do so. For less common formats you +should set the content-type -- which we'll cover in a moment. + +Attaching Existing Files +~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that already exist, either on disk or at a URL can be attached to a +message with just one line of code, using ``Swift_Attachment::fromPath()``. + +You can attach files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can attach files from other +websites. + +The attachment will be presented to the recipient as a downloadable file with +the same filename as the one you attached:: + + // Create the attachment + // * Note that you can technically leave the content-type parameter out + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg', 'image/jpeg'); + + // Attach it to the message + $message->attach($attachment); + + // The two statements above could be written in one line instead + $message->attach(Swift_Attachment::fromPath('/path/to/image.jpg')); + + // You can attach files from a URL if allow_url_fopen is on in php.ini + $message->attach(Swift_Attachment::fromPath('http://site.tld/logo.png')); + +Setting the Filename +~~~~~~~~~~~~~~~~~~~~ + +Usually you don't need to explicitly set the filename of an attachment because +the name of the attached file will be used by default, but if you want to set +the filename you use the ``setFilename()`` method of the Attachment. + +The attachment will be attached in the normal way, but meta-data sent inside +the email will rename the file to something else:: + + // Create the attachment and call its setFilename() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setFilename('cool.jpg'); + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setFilename('cool.jpg') + ); + +Attaching Dynamic Content +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that are generated at runtime, such as PDF documents or images created +via GD can be attached directly to a message without writing them out to disk. +Use ``Swift_Attachment`` directly. + +The attachment will be presented to the recipient as a downloadable file +with the filename and content-type you specify:: + + // Create your file contents in the normal way, but don't write them to disk + $data = create_my_pdf_data(); + + // Create the attachment with your data + $attachment = new Swift_Attachment($data, 'my-file.pdf', 'application/pdf'); + + // Attach it to the message + $message->attach($attachment); + + + // You can alternatively use method chaining to build the attachment + $attachment = (new Swift_Attachment()) + ->setFilename('my-file.pdf') + ->setContentType('application/pdf') + ->setBody($data) + ; + +.. note:: + + If you would usually write the file to disk anyway you should just attach + it with ``Swift_Attachment::fromPath()`` since this will use less memory. + +Changing the Disposition +~~~~~~~~~~~~~~~~~~~~~~~~ + +Attachments just appear as files that can be saved to the Desktop if desired. +You can make attachment appear inline where possible by using the +``setDisposition()`` method of an attachment. + +The attachment will be displayed within the email viewing window if the mail +client knows how to display it:: + + // Create the attachment and call its setDisposition() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setDisposition('inline'); + + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setDisposition('inline') + ); + +.. note:: + + If you try to create an inline attachment for a non-displayable file type + such as a ZIP file, the mail client should just present the attachment as + normal. + +Embedding Inline Media Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Often, people want to include an image or other content inline with a HTML +message. It's easy to do this with HTML linking to remote resources, but this +approach is usually blocked by mail clients. Swift Mailer allows you to embed +your media directly into the message. + +Mail clients usually block downloads from remote resources because this +technique was often abused as a mean of tracking who opened an email. If +you're sending a HTML email and you want to include an image in the message +another approach you can take is to embed the image directly. + +Swift Mailer makes embedding files into messages extremely streamlined. You +embed a file by calling the ``embed()`` method of the message, +which returns a value you can use in a ``src`` or +``href`` attribute in your HTML. + +Just like with attachments, it's possible to embed dynamically generated +content without having an existing file available. + +The embedded files are sent in the email as a special type of attachment that +has a unique ID used to reference them within your HTML attributes. On mail +clients that do not support embedded files they may appear as attachments. + +Although this is commonly done for images, in theory it will work for any +displayable (or playable) media type. Support for other media types (such as +video) is dependent on the mail client however. + +Embedding Existing Files +........................ + +Files that already exist, either on disk or at a URL can be embedded in a +message with just one line of code, using ``Swift_EmbeddedFile::fromPath()``. + +You can embed files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can embed files from other websites. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute:: + + // Create the message + $message = new Swift_Message('My subject'); + + // Set the body + $message->setBody( + '' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + + // You can embed files from a URL if allow_url_fopen is on in php.ini + $message->setBody( + '' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' + ); + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one another. + ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute:: + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(Swift_Image::fromPath('image.png')); + + $message->setBody( + '' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Embedding Dynamic Content +......................... + +Images that are generated at runtime, such as images created via GD can be +embedded directly to a message without writing them out to disk. Use the +standard ``new Swift_Image()`` method. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute:: + + // Create your file contents in the normal way, but don't write them to disk + $img_data = create_my_image_data(); + + // Create the message + $message = new Swift_Message('My subject'); + + // Set the body + $message->setBody( + '' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one another. + ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute:: + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(new Swift_Image($img_data, 'image.jpg', 'image/jpeg')); + + $message->setBody( + '' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Adding Recipients to Your Message +--------------------------------- + +Recipients are specified within the message itself via ``setTo()``, ``setCc()`` +and ``setBcc()``. Swift Mailer reads these recipients from the message when it +gets sent so that it knows where to send the message to. + +Message recipients are one of three types: + +* ``To:`` recipients -- the primary recipients (required) + +* ``Cc:`` recipients -- receive a copy of the message (optional) + +* ``Bcc:`` recipients -- hidden from other recipients (optional) + +Each type can contain one, or several addresses. It's possible to list only the +addresses of the recipients, or you can personalize the address by providing +the real name of the recipient. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using:: + use Egulias\EmailValidator\EmailValidator; + use Egulias\EmailValidator\Validation\RFCValidation; + + $validator = new EmailValidator(); + $validator->isValid("example@example.com", new RFCValidation()); //true +and only adding addresses that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and +``setBcc()`` calls in a try-catch block and handle the +``Swift_RfcComplianceException`` in the catch block. + +.. sidebar:: Syntax for Addresses + + If you only wish to refer to a single email address (for example your + ``From:`` address) then you can just use a string:: + + $message->setFrom('some@address.tld'); + + If you want to include a name then you must use an associative array:: + + $message->setFrom(['some@address.tld' => 'The Name']); + + If you want to include multiple addresses then you must use an array:: + + $message->setTo(['some@address.tld', 'other@address.tld']); + + You can mix personalized (addresses with a name) and non-personalized + addresses in the same list by mixing the use of associative and + non-associative array syntax:: + + $message->setTo([ + 'recipient-with-name@example.org' => 'Recipient Name One', + 'no-name@example.org', // Note that this is not a key-value pair + 'named-recipient@example.org' => 'Recipient Name Two' + ]); + +Setting ``To:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``To:`` recipients are required in a message and are set with the ``setTo()`` +or ``addTo()`` methods of the message. + +To set ``To:`` recipients, create the message object using either ``new +Swift_Message( ... )``, then call the ``setTo()`` method with a complete array +of addresses, or use the ``addTo()`` method to iteratively add recipients. + +The ``setTo()`` method accepts input in various formats as described earlier in +this chapter. The ``addTo()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``To:`` recipients are visible in the message headers and will be seen by the +other recipients:: + + // Using setTo() to set all recipients in one go + $message->setTo([ + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + ]); + +.. note:: + + Multiple calls to ``setTo()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add + recipients, use the ``addTo()`` method:: + + // Using addTo() to add recipients iteratively + $message->addTo('person1@example.org'); + $message->addTo('person2@example.org', 'Person 2 Name'); + +Setting ``Cc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Cc:`` recipients are set with the ``setCc()`` or ``addCc()`` methods of the +message. + +To set ``Cc:`` recipients, create the message object using either ``new +Swift_Message( ... )``, then call the ``setCc()`` method with a complete array +of addresses, or use the ``addCc()`` method to iteratively add recipients. + +The ``setCc()`` method accepts input in various formats as described earlier in +this chapter. The ``addCc()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``Cc:`` recipients are visible in the message headers and will be seen by the +other recipients:: + + // Using setTo() to set all recipients in one go + $message->setTo([ + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + ]); + +.. note:: + + Multiple calls to ``setCc()`` will not add new recipients -- each call + overrides the previous calls. If you want to iteratively add Cc: + recipients, use the ``addCc()`` method:: + + // Using addCc() to add recipients iteratively + $message->addCc('person1@example.org'); + $message->addCc('person2@example.org', 'Person 2 Name'); + +Setting ``Bcc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Bcc:`` recipients receive a copy of the message without anybody else knowing +it, and are set with the ``setBcc()`` or ``addBcc()`` methods of the message. + +To set ``Bcc:`` recipients, create the message object using either ``new +Swift_Message( ... )``, then call the ``setBcc()`` method with a complete array +of addresses, or use the ``addBcc()`` method to iteratively add recipients. + +The ``setBcc()`` method accepts input in various formats as described earlier +in this chapter. The ``addBcc()`` method takes either one or two parameters. +The first being the email address and the second optional parameter being the +name of the recipient. + +Only the individual ``Bcc:`` recipient will see their address in the message +headers. Other recipients (including other ``Bcc:`` recipients) will not see +the address:: + + // Using setBcc() to set all recipients in one go + $message->setBcc([ + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + ]); + +.. note:: + + Multiple calls to ``setBcc()`` will not add new recipients -- each call + overrides the previous calls. If you want to iteratively add Bcc: + recipients, use the ``addBcc()`` method:: + + // Using addBcc() to add recipients iteratively + $message->addBcc('person1@example.org'); + $message->addBcc('person2@example.org', 'Person 2 Name'); + +.. sidebar:: Internationalized Email Addresses + + Traditionally only ASCII characters have been allowed in email addresses. + With the introduction of internationalized domain names (IDNs), non-ASCII + characters may appear in the domain name. By default, Swiftmailer encodes + such domain names in Punycode (e.g. xn--xample-ova.invalid). This is + compatible with all mail servers. + + RFC 6531 introduced an SMTP extension, SMTPUTF8, that allows non-ASCII + characters in email addresses on both sides of the @ sign. To send to such + addresses, your outbound SMTP server must support the SMTPUTF8 extension. + You should use the ``Swift_AddressEncoder_Utf8AddressEncoder`` address + encoder and enable the ``Swift_Transport_Esmtp_SmtpUtf8Handler`` SMTP + extension handler:: + + $smtpUtf8 = new Swift_Transport_Esmtp_SmtpUtf8Handler(); + $transport->setExtensionHandlers([$smtpUtf8]); + $utf8Encoder = new Swift_AddressEncoder_Utf8AddressEncoder(); + $transport->setAddressEncoder($utf8Encoder); + +Specifying Sender Details +------------------------- + +An email must include information about who sent it. Usually this is managed by +the ``From:`` address, however there are other options. + +The sender information is contained in three possible places: + +* ``From:`` -- the address(es) of who wrote the message (required) + +* ``Sender:`` -- the address of the single person who sent the message + (optional) + +* ``Return-Path:`` -- the address where bounces should go to (optional) + +You must always include a ``From:`` address by using ``setFrom()`` on the +message. Swift Mailer will use this as the default ``Return-Path:`` unless +otherwise specified. + +The ``Sender:`` address exists because the person who actually sent the email +may not be the person who wrote the email. It has a higher precedence than the +``From:`` address and will be used as the ``Return-Path:`` unless otherwise +specified. + +Setting the ``From:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``From:`` address is required and is set with the ``setFrom()`` method of the +message. ``From:`` addresses specify who actually wrote the email, and usually +who sent it. + +What most people probably don't realize is that you can have more than one +``From:`` address if more than one person wrote the email -- for example if an +email was put together by a committee. + +The ``From:`` address(es) are visible in the message headers and will be seen +by the recipients. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + +:: + + // Set a single From: address + $message->setFrom('your@address.tld'); + + // Set a From: address including a name + $message->setFrom(['your@address.tld' => 'Your Name']); + + // Set multiple From: addresses if multiple people wrote the email + $message->setFrom([ + 'person1@example.org' => 'Sender One', + 'person2@example.org' => 'Sender Two' + ]); + +Setting the ``Sender:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Sender:`` address specifies who sent the message and is set with the +``setSender()`` method of the message. + +The ``Sender:`` address is visible in the message headers and will be seen by +the recipients. + +This address will be used as the ``Return-Path:`` unless otherwise specified. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + +You must not set more than one sender address on a message because it's not +possible for more than one person to send a single message:: + + $message->setSender('your@address.tld'); + +Setting the ``Return-Path:`` (Bounce) Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Return-Path:`` address specifies where bounce notifications should be +sent and is set with the ``setReturnPath()`` method of the message. + +You can only have one ``Return-Path:`` and it must not include a personal name. + +Bounce notifications will be sent to this address:: + + $message->setReturnPath('bounces@address.tld'); + +Signed/Encrypted Message +------------------------ + +To increase the integrity/security of a message it is possible to sign and/or +encrypt an message using one or multiple signers. + +S/MIME +~~~~~~ + +S/MIME can sign and/or encrypt a message using the OpenSSL extension. + +When signing a message, the signer creates a signature of the entire content of +the message (including attachments). + +The certificate and private key must be PEM encoded, and can be either created +using for example OpenSSL or obtained at an official Certificate Authority (CA). + +**The recipient must have the CA certificate in the list of trusted issuers in +order to verify the signature.** + +**Make sure the certificate supports emailProtection.** + +When using OpenSSL this can done by the including the *-addtrust +emailProtection* parameter when creating the certificate:: + + $message = new Swift_Message(); + + $smimeSigner = new Swift_Signers_SMimeSigner(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem'); + $message->attachSigner($smimeSigner); + +When the private key is secured using a passphrase use the following instead:: + + $message = new Swift_Message(); + + $smimeSigner = new Swift_Signers_SMimeSigner(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', ['/path/to/private-key.pem', 'passphrase']); + $message->attachSigner($smimeSigner); + +By default the signature is added as attachment, making the message still +readable for mailing agents not supporting signed messages. + +Storing the message as binary is also possible but not recommended:: + + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem', PKCS7_BINARY); + +When encrypting the message (also known as enveloping), the entire message +(including attachments) is encrypted using a certificate, and the recipient can +then decrypt the message using corresponding private key. + +Encrypting ensures nobody can read the contents of the message without the +private key. + +Normally the recipient provides a certificate for encrypting and keeping the +decryption key private. + +Using both signing and encrypting is also possible:: + + $message = new Swift_Message(); + + $smimeSigner = new Swift_Signers_SMimeSigner(); + $smimeSigner->setSignCertificate('/path/to/sign-certificate.pem', '/path/to/private-key.pem'); + $smimeSigner->setEncryptCertificate('/path/to/encrypt-certificate.pem'); + $message->attachSigner($smimeSigner); + +The used encryption cipher can be set as the second parameter of +setEncryptCertificate() + +See https://secure.php.net/manual/openssl.ciphers for a list of supported ciphers. + +By default the message is first signed and then encrypted, this can be changed +by adding:: + + $smimeSigner->setSignThenEncrypt(false); + +**Changing this is not recommended as most mail agents don't support this +none-standard way.** + +Only when having trouble with sign then encrypt method, this should be changed. + +Requesting a Read Receipt +------------------------- + +It is possible to request a read-receipt to be sent to an address when the +email is opened. To request a read receipt set the address with +``setReadReceiptTo()``:: + + $message->setReadReceiptTo('your@address.tld'); + +When the email is opened, if the mail client supports it a notification will be +sent to this address. + +.. note:: + + Read receipts won't work for the majority of recipients since many mail + clients auto-disable them. Those clients that will send a read receipt + will make the user aware that one has been requested. + +Setting the Character Set +------------------------- + +The character set of the message (and its MIME parts) is set with the +``setCharset()`` method. You can also change the global default of UTF-8 by +working with the ``Swift_Preferences`` class. + +Swift Mailer will default to the UTF-8 character set unless otherwise +overridden. UTF-8 will work in most instances since it includes all of the +standard US keyboard characters in addition to most international characters. + +It is absolutely vital however that you know what character set your message +(or it's MIME parts) are written in otherwise your message may be received +completely garbled. + +There are two places in Swift Mailer where you can change the character set: + +* In the ``Swift_Preferences`` class + +* On each individual message and/or MIME part + +To set the character set of your Message: + +* Change the global UTF-8 setting by calling + ``Swift_Preferences::setCharset()``; or + +* Call the ``setCharset()`` method on the message or the MIME part:: + + // Approach 1: Change the global setting (suggested) + Swift_Preferences::getInstance()->setCharset('iso-8859-2'); + + // Approach 2: Call the setCharset() method of the message + $message = (new Swift_Message()) + ->setCharset('iso-8859-2'); + + // Approach 3: Specify the charset when setting the body + $message->setBody('My body', 'text/html', 'iso-8859-2'); + + // Approach 4: Specify the charset for each part added + $message->addPart('My part', 'text/plain', 'iso-8859-2'); + +Setting the Encoding +-------------------- + +The body of each MIME part needs to be encoded. Binary attachments are encoded +in base64 using the ``Swift_Mime_ContentEncoder_Base64ContentEncoder``. Text +parts are traditionally encoded in quoted-printable using +``Swift_Mime_ContentEncoder_QpContentEncoder`` or +``Swift_Mime_ContentEncoder_NativeQpContentEncoder``. + +The encoder of the message or MIME part is set with the ``setEncoder()`` method. + +Quoted-printable is the safe choice, because it converts 8-bit text as 7-bit. +Most modern SMTP servers support 8-bit text. This is advertised via the 8BITMIME +SMTP extension. If your outbound SMTP server supports this SMTP extension, and +it supports downgrading the message (e.g converting to quoted-printable on the +fly) when delivering to a downstream server that does not support the extension, +you may wish to use ``Swift_Mime_ContentEncoder_PlainContentEncoder`` in +``8bit`` mode instead. This has the advantage that the source data is slightly +more readable and compact, especially for non-Western languages. + + $eightBitMime = new Swift_Transport_Esmtp_EightBitMimeHandler(); + $transport->setExtensionHandlers([$eightBitMime]); + $plainEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit'); + $message->setEncoder($plainEncoder); + +Setting the Line Length +----------------------- + +The length of lines in a message can be changed by using the +``setMaxLineLength()`` method on the message:: + + $message->setMaxLineLength(1000); + +Swift Mailer defaults to using 78 characters per line in a message. This is +done for historical reasons and so that the message can be easily viewed in +plain-text terminals + +Lines that are longer than the line length specified will be wrapped between +words. + +.. note:: + + You should never set a maximum length longer than 1000 characters + according to RFC 2822. Doing so could have unspecified side-effects such + as truncating parts of your message when it is transported between SMTP + servers. + +Setting the Message Priority +---------------------------- + +You can change the priority of the message with ``setPriority()``. Setting the +priority will not change the way your email is sent -- it is purely an +indicative setting for the recipient:: + + // Indicate "High" priority + $message->setPriority(2); + +The priority of a message is an indication to the recipient what significance +it has. Swift Mailer allows you to set the priority by calling the +``setPriority`` method. This method takes an integer value between 1 and 5: + +* ``Swift_Mime_SimpleMessage::PRIORITY_HIGHEST``: 1 +* ``Swift_Mime_SimpleMessage::PRIORITY_HIGH``: 2 +* ``Swift_Mime_SimpleMessage::PRIORITY_NORMAL``: 3 +* ``Swift_Mime_SimpleMessage::PRIORITY_LOW``: 4 +* ``Swift_Mime_SimpleMessage::PRIORITY_LOWEST``: 5 + +:: + + // Or use the constant to be more explicit + $message->setPriority(Swift_Mime_SimpleMessage::PRIORITY_HIGH); diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/plugins.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/plugins.rst new file mode 100644 index 0000000..548b07f --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/plugins.rst @@ -0,0 +1,337 @@ +Plugins +======= + +Plugins exist to extend, or modify the behaviour of Swift Mailer. They respond +to Events that are fired within the Transports during sending. + +There are a number of Plugins provided as part of the base Swift Mailer package +and they all follow a common interface to respond to Events fired within the +library. Interfaces are provided to "listen" to each type of Event fired and to +act as desired when a listened-to Event occurs. + +Although several plugins are provided with Swift Mailer out-of-the-box, the +Events system has been specifically designed to make it easy for experienced +object-oriented developers to write their own plugins in order to achieve +goals that may not be possible with the base library. + +AntiFlood Plugin +---------------- + +Many SMTP servers have limits on the number of messages that may be sent during +any single SMTP connection. The AntiFlood plugin provides a way to stay within +this limit while still managing a large number of emails. + +A typical limit for a single connection is 100 emails. If the server you +connect to imposes such a limit, it expects you to disconnect after that number +of emails has been sent. You could manage this manually within a loop, but the +AntiFlood plugin provides the necessary wrapper code so that you don't need to +worry about this logic. + +Regardless of limits imposed by the server, it's usually a good idea to be +conservative with the resources of the SMTP server. Sending will become +sluggish if the server is being over-used so using the AntiFlood plugin will +not be a bad idea even if no limits exist. + +The AntiFlood plugin's logic is basically to disconnect and the immediately +re-connect with the SMTP server every X number of emails sent, where X is a +number you specify to the plugin. + +You can also specify a time period in seconds that Swift Mailer should pause +for between the disconnect/re-connect process. It's a good idea to pause for a +short time (say 30 seconds every 100 emails) simply to give the SMTP server a +chance to process its queue and recover some resources. + +Using the AntiFlood Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The AntiFlood Plugin -- like all plugins -- is added with the Mailer class's +``registerPlugin()`` method. It takes two constructor parameters: the number of +emails to pause after, and optionally the number of seconds to pause for. + +When Swift Mailer sends messages it will count the number of messages that have +been sent since the last re-connect. Once the number hits your specified +threshold it will disconnect and re-connect, optionally pausing for a specified +amount of time:: + + // Create the Mailer using any Transport + $mailer = new Swift_Mailer( + new Swift_SmtpTransport('smtp.example.org', 25) + ); + + // Use AntiFlood to re-connect after 100 emails + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100)); + + // And specify a time in seconds to pause for (30 secs) + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100, 30)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Throttler Plugin +---------------- + +If your SMTP server has restrictions in place to limit the rate at which you +send emails, then your code will need to be aware of this rate-limiting. The +Throttler plugin makes Swift Mailer run at a rate-limited speed. + +Many shared hosts don't open their SMTP servers as a free-for-all. Usually they +have policies in place (probably to discourage spammers) that only allow you to +send a fixed number of emails per-hour/day. + +The Throttler plugin supports two modes of rate-limiting and with each, you +will need to do that math to figure out the values you want. The plugin can +limit based on the number of emails per minute, or the number of +bytes-transferred per-minute. + +Using the Throttler Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Throttler Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It has two required constructor parameters that +tell it how to do its rate-limiting. + +When Swift Mailer sends messages it will keep track of the rate at which +sending messages is occurring. If it realises that sending is happening too +fast, it will cause your program to ``sleep()`` for enough time to average out +the rate:: + + // Create the Mailer using any Transport + $mailer = new Swift_Mailer( + new Swift_SmtpTransport('smtp.example.org', 25) + ); + + // Rate limit to 100 emails per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE + )); + + // Rate limit to 10MB per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 1024 * 1024 * 10, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE + )); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Logger Plugin +------------- + +The Logger plugins helps with debugging during the process of sending. It can +help to identify why an SMTP server is rejecting addresses, or any other +hard-to-find problems that may arise. + +The Logger plugin comes in two parts. There's the plugin itself, along with one +of a number of possible Loggers that you may choose to use. For example, the +logger may output messages directly in realtime, or it may capture messages in +an array. + +One other notable feature is the way in which the Logger plugin changes +Exception messages. If Exceptions are being thrown but the error message does +not provide conclusive information as to the source of the problem (such as an +ambiguous SMTP error) the Logger plugin includes the entire SMTP transcript in +the error message so that debugging becomes a simpler task. + +There are a few available Loggers included with Swift Mailer, but writing your +own implementation is incredibly simple and is achieved by creating a short +class that implements the ``Swift_Plugins_Logger`` interface. + +* ``Swift_Plugins_Loggers_ArrayLogger``: Keeps a collection of log messages + inside an array. The array content can be cleared or dumped out to the screen. + +* ``Swift_Plugins_Loggers_EchoLogger``: Prints output to the screen in + realtime. Handy for very rudimentary debug output. + +Using the Logger Plugin +~~~~~~~~~~~~~~~~~~~~~~~ + +The Logger Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It accepts an instance of ``Swift_Plugins_Logger`` +in its constructor. + +When Swift Mailer sends messages it will keep a log of all the interactions +with the underlying Transport being used. Depending upon the Logger that has +been used the behaviour will differ, but all implementations offer a way to get +the contents of the log:: + + // Create the Mailer using any Transport + $mailer = new Swift_Mailer( + new Swift_SmtpTransport('smtp.example.org', 25) + ); + + // To use the ArrayLogger + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Or to use the Echo Logger + $logger = new Swift_Plugins_Loggers_EchoLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + + // Dump the log contents + // NOTE: The EchoLogger dumps in realtime so dump() does nothing for it + echo $logger->dump(); + +Decorator Plugin +---------------- + +Often there's a need to send the same message to multiple recipients, but with +tiny variations such as the recipient's name being used inside the message +body. The Decorator plugin aims to provide a solution for allowing these small +differences. + +The decorator plugin works by intercepting the sending process of Swift Mailer, +reading the email address in the To: field and then looking up a set of +replacements for a template. + +While the use of this plugin is simple, it is probably the most commonly +misunderstood plugin due to the way in which it works. The typical mistake +users make is to try registering the plugin multiple times (once for each +recipient) -- inside a loop for example. This is incorrect. + +The Decorator plugin should be registered just once, but containing the list of +all recipients prior to sending. It will use this list of recipients to find +the required replacements during sending. + +Using the Decorator Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the Decorator plugin, simply create an associative array of replacements +based on email addresses and then use the mailer's ``registerPlugin()`` method +to add the plugin. + +First create an associative array of replacements based on the email addresses +you'll be sending the message to. + +.. note:: + + The replacements array becomes a 2-dimensional array whose keys are the + email addresses and whose values are an associative array of replacements + for that email address. The curly braces used in this example can be any + type of syntax you choose, provided they match the placeholders in your + email template:: + + $replacements = []; + foreach ($users as $user) { + $replacements[$user['email']] = [ + '{username}'=>$user['username'], + '{resetcode}'=>$user['resetcode'] + ]; + } + +Now create an instance of the Decorator plugin using this array of replacements +and then register it with the Mailer. Do this only once! + +:: + + $decorator = new Swift_Plugins_DecoratorPlugin($replacements); + + $mailer->registerPlugin($decorator); + +When you create your message, replace elements in the body (and/or the subject +line) with your placeholders:: + + $message = (new Swift_Message()) + ->setSubject('Important notice for {username}') + ->setBody( + "Hello {username}, you requested to reset your password.\n" . + "Please visit https://example.com/pwreset and use the reset code {resetcode} to set a new password." + ) + ; + + foreach ($users as $user) { + $message->addTo($user['email']); + } + +When you send this message to each of your recipients listed in your +``$replacements`` array they will receive a message customized for just +themselves. For example, the message used above when received may appear like +this to one user: + +.. code-block:: text + + Subject: Important notice for smilingsunshine2009 + + Hello smilingsunshine2009, you requested to reset your password. + Please visit https://example.com/pwreset and use the reset code 183457 to set a new password. + +While another use may receive the message as: + +.. code-block:: text + + Subject: Important notice for billy-bo-bob + + Hello billy-bo-bob, you requested to reset your password. + Please visit https://example.com/pwreset and use the reset code 539127 to set a new password. + +While the decorator plugin provides a means to solve this problem, there are +various ways you could tackle this problem without the need for a plugin. We're +trying to come up with a better way ourselves and while we have several +(obvious) ideas we don't quite have the perfect solution to go ahead and +implement it. Watch this space. + +Providing Your Own Replacements Lookup for the Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Filling an array with replacements may not be the best solution for providing +replacement information to the decorator. If you have a more elegant algorithm +that performs replacement lookups on-the-fly you may provide your own +implementation. + +Providing your own replacements lookup implementation for the Decorator is +simply a matter of passing an instance of +``Swift_Plugins_Decorator_Replacements`` to the decorator plugin's constructor, +rather than passing in an array. + +The Replacements interface is very simple to implement since it has just one +method: ``getReplacementsFor($address)``. + +Imagine you want to look up replacements from a database on-the-fly, you might +provide an implementation that does this. You need to create a small class:: + + class DbReplacements implements Swift_Plugins_Decorator_Replacements { + public function getReplacementsFor($address) { + global $db; // Your PDO instance with a connection to your database + $query = $db->prepare( + "SELECT * FROM `users` WHERE `email` = ?" + ); + + $query->execute([$address]); + + if ($row = $query->fetch(PDO::FETCH_ASSOC)) { + return [ + '{username}'=>$row['username'], + '{resetcode}'=>$row['resetcode'] + ]; + } + } + } + +Now all you need to do is pass an instance of your class into the Decorator +plugin's constructor instead of passing an array:: + + $decorator = new Swift_Plugins_DecoratorPlugin(new DbReplacements()); + + $mailer->registerPlugin($decorator); + +For each message sent, the plugin will call your class' +``getReplacementsFor()`` method to find the array of replacements it needs. + +.. note:: + + If your lookup algorithm is case sensitive, you should transform the + ``$address`` argument as appropriate -- for example by passing it through + ``strtolower()``. diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/doc/sending.rst b/plugins/email/vendor/swiftmailer/swiftmailer/doc/sending.rst new file mode 100644 index 0000000..0104207 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/doc/sending.rst @@ -0,0 +1,453 @@ +Sending Messages +================ + +Quick Reference for Sending a Message +------------------------------------- + +Sending a message is very straightforward. You create a Transport, use it to +create the Mailer, then you use the Mailer to send the message. + +When using ``send()`` the message will be sent just like it would be sent if +you used your mail client. An integer is returned which includes the number of +successful recipients. If none of the recipients could be sent to then zero +will be returned, which equates to a boolean ``false``. If you set two ``To:`` +recipients and three ``Bcc:`` recipients in the message and all of the +recipients are delivered to successfully then the value 5 will be returned:: + + // Create the Transport + $transport = (new Swift_SmtpTransport('smtp.example.org', 25)) + ->setUsername('your username') + ->setPassword('your password') + ; + + /* + You could alternatively use a different transport such as Sendmail: + + // Sendmail + $transport = new Swift_SendmailTransport('/usr/sbin/sendmail -bs'); + */ + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + + // Create a message + $message = (new Swift_Message('Wonderful Subject')) + ->setFrom(['john@doe.com' => 'John Doe']) + ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']) + ->setBody('Here is the message itself') + ; + + // Send the message + $result = $mailer->send($message); + +Transport Types +~~~~~~~~~~~~~~~ + +Transports are the classes in Swift Mailer that are responsible for +communicating with a service in order to deliver a Message. There are several +types of Transport in Swift Mailer, all of which implement the +``Swift_Transport`` interface:: + +* ``Swift_SmtpTransport``: Sends messages over SMTP; Supports Authentication; + Supports Encryption. Very portable; Pleasingly predictable results; Provides + good feedback; + +* ``Swift_SendmailTransport``: Communicates with a locally installed + ``sendmail`` executable (Linux/UNIX). Quick time-to-run; Provides + less-accurate feedback than SMTP; Requires ``sendmail`` installation; + +* ``Swift_LoadBalancedTransport``: Cycles through a collection of the other + Transports to manage load-reduction. Provides graceful fallback if one + Transport fails (e.g. an SMTP server is down); Keeps the load on remote + services down by spreading the work; + +* ``Swift_FailoverTransport``: Works in conjunction with a collection of the + other Transports to provide high-availability. Provides graceful fallback if + one Transport fails (e.g. an SMTP server is down). + +The SMTP Transport +.................. + +The SMTP Transport sends messages over the (standardized) Simple Message +Transfer Protocol. It can deal with encryption and authentication. + +The SMTP Transport, ``Swift_SmtpTransport`` is without doubt the most commonly +used Transport because it will work on 99% of web servers (I just made that +number up, but you get the idea). All the server needs is the ability to +connect to a remote (or even local) SMTP server on the correct port number +(usually 25). + +SMTP servers often require users to authenticate with a username and password +before any mail can be sent to other domains. This is easily achieved using +Swift Mailer with the SMTP Transport. + +SMTP is a protocol -- in other words it's a "way" of communicating a job to be +done (i.e. sending a message). The SMTP protocol is the fundamental basis on +which messages are delivered all over the internet 7 days a week, 365 days a +year. For this reason it's the most "direct" method of sending messages you can +use and it's the one that will give you the most power and feedback (such as +delivery failures) when using Swift Mailer. + +Because SMTP is generally run as a remote service (i.e. you connect to it over +the network/internet) it's extremely portable from server-to-server. You can +easily store the SMTP server address and port number in a configuration file +within your application and adjust the settings accordingly if the code is +moved or if the SMTP server is changed. + +Some SMTP servers -- Google for example -- use encryption for security reasons. +Swift Mailer supports using both SSL and TLS encryption settings. + +Using the SMTP Transport +^^^^^^^^^^^^^^^^^^^^^^^^ + +The SMTP Transport is easy to use. Most configuration options can be set with +the constructor. + +To use the SMTP Transport you need to know which SMTP server your code needs to +connect to. Ask your web host if you're not sure. Lots of people ask me who to +connect to -- I really can't answer that since it's a setting that's extremely +specific to your hosting environment. + +A connection to the SMTP server will be established upon the first call to +``send()``:: + + // Create the Transport + $transport = new Swift_SmtpTransport('smtp.example.org', 25); + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + + /* + It's also possible to use multiple method calls + + $transport = (new Swift_SmtpTransport()) + ->setHost('smtp.example.org') + ->setPort(25) + ; + */ + +Encrypted SMTP +^^^^^^^^^^^^^^ + +You can use SSL or TLS encryption with the SMTP Transport by specifying it as a +parameter or with a method call:: + + // Create the Transport + $transport = new Swift_SmtpTransport('smtp.example.org', 587, 'ssl'); + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + +A connection to the SMTP server will be established upon the first call to +``send()``. The connection will be initiated with the correct encryption +settings. + +.. note:: + + For SSL or TLS encryption to work your PHP installation must have + appropriate OpenSSL transports wrappers. You can check if "tls" and/or + "ssl" are present in your PHP installation by using the PHP function + ``stream_get_transports()``. + +.. note:: + If you are using Mailcatcher_, make sure you do not set the encryption + for the ``Swift_SmtpTransport``, since Mailcatcher does not support encryption. + +SMTP with a Username and Password +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some servers require authentication. You can provide a username and password +with ``setUsername()`` and ``setPassword()`` methods:: + + // Create the Transport the call setUsername() and setPassword() + $transport = (new Swift_SmtpTransport('smtp.example.org', 25)) + ->setUsername('username') + ->setPassword('password') + ; + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + +Your username and password will be used to authenticate upon first connect when +``send()`` are first used on the Mailer. + +If authentication fails, an Exception of type ``Swift_TransportException`` will +be thrown. + +.. note:: + + If you need to know early whether or not authentication has failed and an + Exception is going to be thrown, call the ``start()`` method on the + created Transport. + +The Sendmail Transport +...................... + +The Sendmail Transport sends messages by communicating with a locally installed +MTA -- such as ``sendmail``. + +The Sendmail Transport, ``Swift_SendmailTransport`` does not directly connect +to any remote services. It is designed for Linux servers that have ``sendmail`` +installed. The Transport starts a local ``sendmail`` process and sends messages +to it. Usually the ``sendmail`` process will respond quickly as it spools your +messages to disk before sending them. + +The Transport is named the Sendmail Transport for historical reasons +(``sendmail`` was the "standard" UNIX tool for sending e-mail for years). It +will send messages using other transfer agents such as Exim or Postfix despite +its name, provided they have the relevant sendmail wrappers so that they can be +started with the correct command-line flags. + +It's a common misconception that because the Sendmail Transport returns a +result very quickly it must therefore deliver messages to recipients quickly -- +this is not true. It's not slow by any means, but it's certainly not faster +than SMTP when it comes to getting messages to the intended recipients. This is +because sendmail itself sends the messages over SMTP once they have been +quickly spooled to disk. + +The Sendmail Transport has the potential to be just as smart of the SMTP +Transport when it comes to notifying Swift Mailer about which recipients were +rejected, but in reality the majority of locally installed ``sendmail`` +instances are not configured well enough to provide any useful feedback. As +such Swift Mailer may report successful deliveries where they did in fact fail +before they even left your server. + +You can run the Sendmail Transport in two different modes specified by command +line flags: + +* "``-bs``" runs in SMTP mode so theoretically it will act like the SMTP + Transport + +* "``-t``" runs in piped mode with no feedback, but theoretically faster, + though not advised + +You can think of the Sendmail Transport as a sort of asynchronous SMTP +Transport -- though if you have problems with delivery failures you should try +using the SMTP Transport instead. Swift Mailer isn't doing the work here, it's +simply passing the work to somebody else (i.e. ``sendmail``). + +Using the Sendmail Transport +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To use the Sendmail Transport you simply need to call ``new +Swift_SendmailTransport()`` with the command as a parameter. + +To use the Sendmail Transport you need to know where ``sendmail`` or another +MTA exists on the server. Swift Mailer uses a default value of +``/usr/sbin/sendmail``, which should work on most systems. + +You specify the entire command as a parameter (i.e. including the command line +flags). Swift Mailer supports operational modes of "``-bs``" (default) and +"``-t``". + +.. note:: + + If you run sendmail in "``-t``" mode you will get no feedback as to whether + or not sending has succeeded. Use "``-bs``" unless you have a reason not to. + +A sendmail process will be started upon the first call to ``send()``. If the +process cannot be started successfully an Exception of type +``Swift_TransportException`` will be thrown:: + + // Create the Transport + $transport = new Swift_SendmailTransport('/usr/sbin/exim -bs'); + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + +Available Methods for Sending Messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Mailer class offers one method for sending Messages -- ``send()``. + +When a message is sent in Swift Mailer, the Mailer class communicates with +whichever Transport class you have chosen to use. + +Each recipient in the message should either be accepted or rejected by the +Transport. For example, if the domain name on the email address is not +reachable the SMTP Transport may reject the address because it cannot process +it. ``send()`` will return an integer indicating the number of accepted +recipients. + +.. note:: + + It's possible to find out which recipients were rejected -- we'll cover that + later in this chapter. + +Using the ``send()`` Method +........................... + +The ``send()`` method of the ``Swift_Mailer`` class sends a message using +exactly the same logic as your Desktop mail client would use. Just pass it a +Message and get a result. + +The message will be sent just like it would be sent if you used your mail +client. An integer is returned which includes the number of successful +recipients. If none of the recipients could be sent to then zero will be +returned, which equates to a boolean ``false``. If you set two +``To:`` recipients and three ``Bcc:`` recipients in the message and all of the +recipients are delivered to successfully then the value 5 will be returned:: + + // Create the Transport + $transport = new Swift_SmtpTransport('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + + // Create a message + $message = (new Swift_Message('Wonderful Subject')) + ->setFrom(['john@doe.com' => 'John Doe']) + ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']) + ->setBody('Here is the message itself') + ; + + // Send the message + $numSent = $mailer->send($message); + + printf("Sent %d messages\n", $numSent); + + /* Note that often that only the boolean equivalent of the + return value is of concern (zero indicates FALSE) + + if ($mailer->send($message)) + { + echo "Sent\n"; + } + else + { + echo "Failed\n"; + } + + */ + +Sending Emails in Batch +....................... + +If you want to send a separate message to each recipient so that only their own +address shows up in the ``To:`` field, follow the following recipe: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, + or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Iterate over the recipients and send message via the ``send()`` method on + the Mailer object. + +Each recipient of the messages receives a different copy with only their own +email address on the ``To:`` field. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using ``Egulias\EmailValidator\EmailValidator`` (a dependency that is +installed with Swift Mailer) and only adding addresses that validate. Another +way would be to wrap your ``setTo()``, ``setCc()`` and ``setBcc()`` calls in a +try-catch block and handle the ``Swift_RfcComplianceException`` in the catch +block. + +Handling invalid addresses properly is especially important when sending emails +in large batches since a single invalid address might cause an unhandled +exception and stop the execution or your script early. + +.. note:: + + In the following example, two emails are sent. One to each of + ``receiver@domain.org`` and ``other@domain.org``. These recipients will + not be aware of each other:: + + // Create the Transport + $transport = new Swift_SmtpTransport('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = new Swift_Mailer($transport); + + // Create a message + $message = (new Swift_Message('Wonderful Subject')) + ->setFrom(['john@doe.com' => 'John Doe']) + ->setBody('Here is the message itself') + ; + + // Send the message + $failedRecipients = []; + $numSent = 0; + $to = ['receiver@domain.org', 'other@domain.org' => 'A name']; + + foreach ($to as $address => $name) + { + if (is_int($address)) { + $message->setTo($name); + } else { + $message->setTo([$address => $name]); + } + + $numSent += $mailer->send($message, $failedRecipients); + } + + printf("Sent %d messages\n", $numSent); + +Finding out Rejected Addresses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's possible to get a list of addresses that were rejected by the Transport by +using a by-reference parameter to ``send()``. + +As Swift Mailer attempts to send the message to each address given to it, if a +recipient is rejected it will be added to the array. You can pass an existing +array, otherwise one will be created by-reference. + +Collecting the list of recipients that were rejected can be useful in +circumstances where you need to "prune" a mailing list for example when some +addresses cannot be delivered to. + +Getting Failures By-reference +............................. + +Collecting delivery failures by-reference with the ``send()`` method is as +simple as passing a variable name to the method call:: + + $mailer = new Swift_Mailer( ... ); + + $message = (new Swift_Message( ... )) + ->setFrom( ... ) + ->setTo([ + 'receiver@bad-domain.org' => 'Receiver Name', + 'other@domain.org' => 'A name', + 'other-receiver@bad-domain.org' => 'Other Name' + )) + ->setBody( ... ) + ; + + // Pass a variable name to the send() method + if (!$mailer->send($message, $failures)) + { + echo "Failures:"; + print_r($failures); + } + + /* + Failures: + Array ( + 0 => receiver@bad-domain.org, + 1 => other-receiver@bad-domain.org + ) + */ + +If the Transport rejects any of the recipients, the culprit addresses will be +added to the array provided by-reference. + +.. note:: + + If the variable name does not yet exist, it will be initialized as an + empty array and then failures will be added to that array. If the variable + already exists it will be type-cast to an array and failures will be added + to it. + +.. _Mailcatcher: https://mailcatcher.me/ diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php new file mode 100644 index 0000000..ddb7bb2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php @@ -0,0 +1,78 @@ +address = $address; + } + + public function getAddress(): string + { + return $this->address; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php new file mode 100644 index 0000000..7a1420f --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php @@ -0,0 +1,54 @@ +createDependenciesFor('mime.attachment') + ); + + $this->setBody($data, $contentType); + $this->setFilename($filename); + } + + /** + * Create a new Attachment from a filesystem path. + * + * @param string $path + * @param string $contentType optional + * + * @return self + */ + public static function fromPath($path, $contentType = null) + { + return (new self())->setFile( + new Swift_ByteStream_FileByteStream($path), + $contentType + ); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php new file mode 100644 index 0000000..3a69c15 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php @@ -0,0 +1,176 @@ +filters[$key] = $filter; + } + + /** + * Remove an already present StreamFilter based on its $key. + * + * @param string $key + */ + public function removeFilter($key) + { + unset($this->filters[$key]); + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + * + * @throws Swift_IoException + * + * @return int + */ + public function write($bytes) + { + $this->writeBuffer .= $bytes; + foreach ($this->filters as $filter) { + if ($filter->shouldBuffer($this->writeBuffer)) { + return; + } + } + $this->doWrite($this->writeBuffer); + + return ++$this->sequence; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + */ + public function commit() + { + $this->doWrite($this->writeBuffer); + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + */ + public function bind(Swift_InputByteStream $is) + { + $this->mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->mirrors as $k => $stream) { + if ($is === $stream) { + if ('' !== $this->writeBuffer) { + $stream->write($this->writeBuffer); + } + unset($this->mirrors[$k]); + } + } + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + */ + public function flushBuffers() + { + if ('' !== $this->writeBuffer) { + $this->doWrite($this->writeBuffer); + } + $this->flush(); + + foreach ($this->mirrors as $stream) { + $stream->flushBuffers(); + } + } + + /** Run $bytes through all filters */ + private function filter($bytes) + { + foreach ($this->filters as $filter) { + $bytes = $filter->filter($bytes); + } + + return $bytes; + } + + /** Just write the bytes to the stream */ + private function doWrite($bytes) + { + $this->doCommit($this->filter($bytes)); + + foreach ($this->mirrors as $stream) { + $stream->write($bytes); + } + + $this->writeBuffer = ''; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php new file mode 100644 index 0000000..4f3dcc3 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php @@ -0,0 +1,178 @@ +array = $stack; + $this->arraySize = \count($stack); + } elseif (\is_string($stack)) { + $this->write($stack); + } else { + $this->array = []; + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->offset == $this->arraySize) { + return false; + } + + // Don't use array slice + $end = $length + $this->offset; + $end = $this->arraySize < $end ? $this->arraySize : $end; + $ret = ''; + for (; $this->offset < $end; ++$this->offset) { + $ret .= $this->array[$this->offset]; + } + + return $ret; + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + */ + public function write($bytes) + { + $to_add = str_split($bytes); + foreach ($to_add as $value) { + $this->array[] = $value; + } + $this->arraySize = \count($this->array); + + foreach ($this->mirrors as $stream) { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + */ + public function bind(Swift_InputByteStream $is) + { + $this->mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->mirrors as $k => $stream) { + if ($is === $stream) { + unset($this->mirrors[$k]); + } + } + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * + * @param int $byteOffset + * + * @return bool + */ + public function setReadPointer($byteOffset) + { + if ($byteOffset > $this->arraySize) { + $byteOffset = $this->arraySize; + } elseif ($byteOffset < 0) { + $byteOffset = 0; + } + + $this->offset = $byteOffset; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->offset = 0; + $this->array = []; + $this->arraySize = 0; + + foreach ($this->mirrors as $stream) { + $stream->flushBuffers(); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php new file mode 100644 index 0000000..f639121 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php @@ -0,0 +1,214 @@ +path = $path; + $this->mode = $writable ? 'w+b' : 'rb'; + } + + /** + * Get the complete path to the file. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * + * @param int $length + * + * @return string|bool + * + * @throws Swift_IoException + */ + public function read($length) + { + $fp = $this->getReadHandle(); + if (!feof($fp)) { + $bytes = fread($fp, $length); + $this->offset = ftell($fp); + + // If we read one byte after reaching the end of the file + // feof() will return false and an empty string is returned + if ((false === $bytes || '' === $bytes) && feof($fp)) { + $this->resetReadHandle(); + + return false; + } + + return $bytes; + } + + $this->resetReadHandle(); + + return false; + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * + * @param int $byteOffset + * + * @return bool + */ + public function setReadPointer($byteOffset) + { + if (isset($this->reader)) { + $this->seekReadStreamToPosition($byteOffset); + } + $this->offset = $byteOffset; + } + + /** Just write the bytes to the file */ + protected function doCommit($bytes) + { + fwrite($this->getWriteHandle(), $bytes); + $this->resetReadHandle(); + } + + /** Not used */ + protected function flush() + { + } + + /** Get the resource for reading */ + private function getReadHandle() + { + if (!isset($this->reader)) { + $pointer = @fopen($this->path, 'rb'); + if (!$pointer) { + throw new Swift_IoException('Unable to open file for reading ['.$this->path.']'); + } + $this->reader = $pointer; + if (0 != $this->offset) { + $this->getReadStreamSeekableStatus(); + $this->seekReadStreamToPosition($this->offset); + } + } + + return $this->reader; + } + + /** Get the resource for writing */ + private function getWriteHandle() + { + if (!isset($this->writer)) { + if (!$this->writer = fopen($this->path, $this->mode)) { + throw new Swift_IoException('Unable to open file for writing ['.$this->path.']'); + } + } + + return $this->writer; + } + + /** Force a reload of the resource for reading */ + private function resetReadHandle() + { + if (isset($this->reader)) { + fclose($this->reader); + $this->reader = null; + } + } + + /** Check if ReadOnly Stream is seekable */ + private function getReadStreamSeekableStatus() + { + $metas = stream_get_meta_data($this->reader); + $this->seekable = $metas['seekable']; + } + + /** Streams in a readOnly stream ensuring copy if needed */ + private function seekReadStreamToPosition($offset) + { + if (null === $this->seekable) { + $this->getReadStreamSeekableStatus(); + } + if (false === $this->seekable) { + $currentPos = ftell($this->reader); + if ($currentPos < $offset) { + $toDiscard = $offset - $currentPos; + fread($this->reader, $toDiscard); + + return; + } + $this->copyReadStream(); + } + fseek($this->reader, $offset, SEEK_SET); + } + + /** Copy a readOnly Stream to ensure seekability */ + private function copyReadStream() + { + if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) { + /* We have opened a php:// Stream Should work without problem */ + } elseif (\function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) { + /* We have opened a tmpfile */ + } else { + throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available'); + } + $currentPos = ftell($this->reader); + fclose($this->reader); + $source = fopen($this->path, 'rb'); + if (!$source) { + throw new Swift_IoException('Unable to open file for copying ['.$this->path.']'); + } + fseek($tmpFile, 0, SEEK_SET); + while (!feof($source)) { + fwrite($tmpFile, fread($source, 4096)); + } + fseek($tmpFile, $currentPos, SEEK_SET); + fclose($source); + $this->reader = $tmpFile; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php new file mode 100644 index 0000000..0dc6190 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php @@ -0,0 +1,52 @@ +getPath()))) { + throw new Swift_IoException('Failed to get temporary file content.'); + } + + return $content; + } + + public function __destruct() + { + if (file_exists($this->getPath())) { + @unlink($this->getPath()); + } + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php new file mode 100644 index 0000000..4267adb --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php @@ -0,0 +1,67 @@ + + */ +interface Swift_CharacterReader +{ + const MAP_TYPE_INVALID = 0x01; + const MAP_TYPE_FIXED_LEN = 0x02; + const MAP_TYPE_POSITIONS = 0x03; + + /** + * Returns the complete character map. + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars); + + /** + * Returns the mapType, see constants. + * + * @return int + */ + public function getMapType(); + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param int[] $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size); + + /** + * Returns the number of bytes which should be read to start each character. + * + * For fixed width character sets this should be the number of octets-per-character. + * For multibyte character sets this will probably be 1. + * + * @return int + */ + public function getInitialByteSize(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php new file mode 100644 index 0000000..3e055af --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php @@ -0,0 +1,97 @@ + + */ +class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader +{ + /** + * The number of bytes in a single character. + * + * @var int + */ + private $width; + + /** + * Creates a new GenericFixedWidthReader using $width bytes per character. + * + * @param int $width + */ + public function __construct($width) + { + $this->width = $width; + } + + /** + * Returns the complete character map. + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + $strlen = \strlen($string); + // % and / are CPU intensive, so, maybe find a better way + $ignored = $strlen % $this->width; + $ignoredChars = $ignored ? substr($string, -$ignored) : ''; + $currentMap = $this->width; + + return ($strlen - $ignored) / $this->width; + } + + /** + * Returns the mapType. + * + * @return int + */ + public function getMapType() + { + return self::MAP_TYPE_FIXED_LEN; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $needed = $this->width - $size; + + return $needed > -1 ? $needed : -1; + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return $this->width; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php new file mode 100644 index 0000000..ffc05f7 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php @@ -0,0 +1,84 @@ + "\x07F") { + // Invalid char + $currentMap[$i + $startOffset] = $string[$i]; + } + } + + return $strlen; + } + + /** + * Returns mapType. + * + * @return int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_INVALID; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $byte = reset($bytes); + if (1 == \count($bytes) && $byte >= 0x00 && $byte <= 0x7F) { + return 0; + } + + return -1; + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php new file mode 100644 index 0000000..da37e0d --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php @@ -0,0 +1,176 @@ + + */ +class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader +{ + /** Pre-computed for optimization */ + private static $length_map = [ + // N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x4N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x5N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x6N + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x7N + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x8N + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x9N + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xAN + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xBN + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xCN + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xDN + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xEN + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // 0xFN + ]; + + private static $s_length_map = [ + "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1, + "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1, + "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1, + "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1, + "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1, + "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1, + "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1, + "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1, + "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1, + "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1, + "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1, + "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1, + "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1, + "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1, + "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1, + "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1, + "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0, + "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0, + "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0, + "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0, + "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0, + "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0, + "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0, + "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0, + "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2, + "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2, + "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2, + "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2, + "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3, + "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3, + "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4, + "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0, + ]; + + /** + * Returns the complete character map. + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + if (!isset($currentMap['i']) || !isset($currentMap['p'])) { + $currentMap['p'] = $currentMap['i'] = []; + } + + $strlen = \strlen($string); + $charPos = \count($currentMap['p']); + $foundChars = 0; + $invalid = false; + for ($i = 0; $i < $strlen; ++$i) { + $char = $string[$i]; + $size = self::$s_length_map[$char]; + if (0 == $size) { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue; + } else { + if (true === $invalid) { + /* We mark the chars as invalid and start a new char */ + $currentMap['p'][$charPos + $foundChars] = $startOffset + $i; + $currentMap['i'][$charPos + $foundChars] = true; + ++$foundChars; + $invalid = false; + } + if (($i + $size) > $strlen) { + $ignoredChars = substr($string, $i); + break; + } + for ($j = 1; $j < $size; ++$j) { + $char = $string[$i + $j]; + if ($char > "\x7F" && $char < "\xC0") { + // Valid - continue parsing + } else { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue 2; + } + } + /* Ok we got a complete char here */ + $currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size; + $i += $j - 1; + ++$foundChars; + } + } + + return $foundChars; + } + + /** + * Returns mapType. + * + * @return int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_POSITIONS; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + if ($size < 1) { + return -1; + } + $needed = self::$length_map[$bytes[0]] - $size; + + return $needed > -1 ? $needed : -1; + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php new file mode 100644 index 0000000..15b6c69 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php @@ -0,0 +1,26 @@ +init(); + } + + public function __wakeup() + { + $this->init(); + } + + public function init() + { + if (\count(self::$map) > 0) { + return; + } + + $prefix = 'Swift_CharacterReader_'; + + $singleByte = [ + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => [1], + ]; + + $doubleByte = [ + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => [2], + ]; + + $fourBytes = [ + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => [4], + ]; + + // Utf-8 + self::$map['utf-?8'] = [ + 'class' => $prefix.'Utf8Reader', + 'constructor' => [], + ]; + + //7-8 bit charsets + self::$map['(us-)?ascii'] = $singleByte; + self::$map['(iso|iec)-?8859-?[0-9]+'] = $singleByte; + self::$map['windows-?125[0-9]'] = $singleByte; + self::$map['cp-?[0-9]+'] = $singleByte; + self::$map['ansi'] = $singleByte; + self::$map['macintosh'] = $singleByte; + self::$map['koi-?7'] = $singleByte; + self::$map['koi-?8-?.+'] = $singleByte; + self::$map['mik'] = $singleByte; + self::$map['(cork|t1)'] = $singleByte; + self::$map['v?iscii'] = $singleByte; + + //16 bits + self::$map['(ucs-?2|utf-?16)'] = $doubleByte; + + //32 bits + self::$map['(ucs-?4|utf-?32)'] = $fourBytes; + + // Fallback + self::$map['.*'] = $singleByte; + } + + /** + * Returns a CharacterReader suitable for the charset applied. + * + * @param string $charset + * + * @return Swift_CharacterReader + */ + public function getReaderFor($charset) + { + $charset = strtolower(trim($charset)); + foreach (self::$map as $pattern => $spec) { + $re = '/^'.$pattern.'$/D'; + if (preg_match($re, $charset)) { + if (!\array_key_exists($pattern, self::$loaded)) { + $reflector = new ReflectionClass($spec['class']); + if ($reflector->getConstructor()) { + $reader = $reflector->newInstanceArgs($spec['constructor']); + } else { + $reader = $reflector->newInstance(); + } + self::$loaded[$pattern] = $reader; + } + + return self::$loaded[$pattern]; + } + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php new file mode 100644 index 0000000..c9d8a07 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php @@ -0,0 +1,87 @@ +setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /** + * Set the character set used in this CharacterStream. + * + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->charset = $charset; + $this->charReader = null; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + */ + public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory) + { + $this->charReaderFactory = $factory; + } + + /** + * Overwrite this character stream using the byte sequence in the byte stream. + * + * @param Swift_OutputByteStream $os output stream to read from + */ + public function importByteStream(Swift_OutputByteStream $os) + { + if (!isset($this->charReader)) { + $this->charReader = $this->charReaderFactory + ->getReaderFor($this->charset); + } + + $startLength = $this->charReader->getInitialByteSize(); + while (false !== $bytes = $os->read($startLength)) { + $c = []; + for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) { + $c[] = self::$byteMap[$bytes[$i]]; + } + $size = \count($c); + $need = $this->charReader + ->validateByteSequence($c, $size); + if ($need > 0 && + false !== $bytes = $os->read($need)) { + for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) { + $c[] = self::$byteMap[$bytes[$i]]; + } + } + $this->array[] = $c; + ++$this->array_size; + } + } + + /** + * Import a string a bytes into this CharacterStream, overwriting any existing + * data in the stream. + * + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * Read $length characters from the stream and move the internal pointer + * $length further into the stream. + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->offset == $this->array_size) { + return false; + } + + // Don't use array slice + $arrays = []; + $end = $length + $this->offset; + for ($i = $this->offset; $i < $end; ++$i) { + if (!isset($this->array[$i])) { + break; + } + $arrays[] = $this->array[$i]; + } + $this->offset += $i - $this->offset; // Limit function calls + $chars = false; + foreach ($arrays as $array) { + $chars .= implode('', array_map('chr', $array)); + } + + return $chars; + } + + /** + * Read $length characters from the stream and return a 1-dimensional array + * containing there octet values. + * + * @param int $length + * + * @return int[] + */ + public function readBytes($length) + { + if ($this->offset == $this->array_size) { + return false; + } + $arrays = []; + $end = $length + $this->offset; + for ($i = $this->offset; $i < $end; ++$i) { + if (!isset($this->array[$i])) { + break; + } + $arrays[] = $this->array[$i]; + } + $this->offset += ($i - $this->offset); // Limit function calls + + return array_merge(...$arrays); + } + + /** + * Write $chars to the end of the stream. + * + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->charReader)) { + $this->charReader = $this->charReaderFactory->getReaderFor( + $this->charset); + } + + $startLength = $this->charReader->getInitialByteSize(); + + $fp = fopen('php://memory', 'w+b'); + fwrite($fp, $chars); + unset($chars); + fseek($fp, 0, SEEK_SET); + + $buffer = [0]; + $buf_pos = 1; + $buf_len = 1; + $has_datas = true; + do { + $bytes = []; + // Buffer Filing + if ($buf_len - $buf_pos < $startLength) { + $buf = array_splice($buffer, $buf_pos); + $new = $this->reloadBuffer($fp, 100); + if ($new) { + $buffer = array_merge($buf, $new); + $buf_len = \count($buffer); + $buf_pos = 0; + } else { + $has_datas = false; + } + } + if ($buf_len - $buf_pos > 0) { + $size = 0; + for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) { + ++$size; + $bytes[] = $buffer[$buf_pos++]; + } + $need = $this->charReader->validateByteSequence( + $bytes, $size); + if ($need > 0) { + if ($buf_len - $buf_pos < $need) { + $new = $this->reloadBuffer($fp, $need); + + if ($new) { + $buffer = array_merge($buffer, $new); + $buf_len = \count($buffer); + } + } + for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) { + $bytes[] = $buffer[$buf_pos++]; + } + } + $this->array[] = $bytes; + ++$this->array_size; + } + } while ($has_datas); + + fclose($fp); + } + + /** + * Move the internal pointer to $charOffset in the stream. + * + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($charOffset > $this->array_size) { + $charOffset = $this->array_size; + } elseif ($charOffset < 0) { + $charOffset = 0; + } + $this->offset = $charOffset; + } + + /** + * Empty the stream and reset the internal pointer. + */ + public function flushContents() + { + $this->offset = 0; + $this->array = []; + $this->array_size = 0; + } + + private function reloadBuffer($fp, $len) + { + if (!feof($fp) && false !== ($bytes = fread($fp, $len))) { + $buf = []; + for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) { + $buf[] = self::$byteMap[$bytes[$i]]; + } + + return $buf; + } + + return false; + } + + private static function initializeMaps() + { + if (!isset(self::$charMap)) { + self::$charMap = []; + for ($byte = 0; $byte < 256; ++$byte) { + self::$charMap[$byte] = \chr($byte); + } + self::$byteMap = array_flip(self::$charMap); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php new file mode 100644 index 0000000..7578dda --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php @@ -0,0 +1,262 @@ + + */ +class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream +{ + /** + * The char reader (lazy-loaded) for the current charset. + * + * @var Swift_CharacterReader + */ + private $charReader; + + /** + * A factory for creating CharacterReader instances. + * + * @var Swift_CharacterReaderFactory + */ + private $charReaderFactory; + + /** + * The character set this stream is using. + * + * @var string + */ + private $charset; + + /** + * The data's stored as-is. + * + * @var string + */ + private $datas = ''; + + /** + * Number of bytes in the stream. + * + * @var int + */ + private $datasSize = 0; + + /** + * Map. + * + * @var mixed + */ + private $map; + + /** + * Map Type. + * + * @var int + */ + private $mapType = 0; + + /** + * Number of characters in the stream. + * + * @var int + */ + private $charCount = 0; + + /** + * Position in the stream. + * + * @var int + */ + private $currentPos = 0; + + /** + * Constructor. + * + * @param string $charset + */ + public function __construct(Swift_CharacterReaderFactory $factory, $charset) + { + $this->setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /* -- Changing parameters of the stream -- */ + + /** + * Set the character set used in this CharacterStream. + * + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->charset = $charset; + $this->charReader = null; + $this->mapType = 0; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + */ + public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory) + { + $this->charReaderFactory = $factory; + } + + /** + * @see Swift_CharacterStream::flushContents() + */ + public function flushContents() + { + $this->datas = null; + $this->map = null; + $this->charCount = 0; + $this->currentPos = 0; + $this->datasSize = 0; + } + + /** + * @see Swift_CharacterStream::importByteStream() + */ + public function importByteStream(Swift_OutputByteStream $os) + { + $this->flushContents(); + $blocks = 512; + $os->setReadPointer(0); + while (false !== ($read = $os->read($blocks))) { + $this->write($read); + } + } + + /** + * @see Swift_CharacterStream::importString() + * + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * @see Swift_CharacterStream::read() + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->currentPos >= $this->charCount) { + return false; + } + $ret = false; + $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length; + switch ($this->mapType) { + case Swift_CharacterReader::MAP_TYPE_FIXED_LEN: + $len = $length * $this->map; + $ret = substr($this->datas, + $this->currentPos * $this->map, + $len); + $this->currentPos += $length; + break; + + case Swift_CharacterReader::MAP_TYPE_INVALID: + $ret = ''; + for (; $this->currentPos < $length; ++$this->currentPos) { + if (isset($this->map[$this->currentPos])) { + $ret .= '?'; + } else { + $ret .= $this->datas[$this->currentPos]; + } + } + break; + + case Swift_CharacterReader::MAP_TYPE_POSITIONS: + $end = $this->currentPos + $length; + $end = $end > $this->charCount ? $this->charCount : $end; + $ret = ''; + $start = 0; + if ($this->currentPos > 0) { + $start = $this->map['p'][$this->currentPos - 1]; + } + $to = $start; + for (; $this->currentPos < $end; ++$this->currentPos) { + if (isset($this->map['i'][$this->currentPos])) { + $ret .= substr($this->datas, $start, $to - $start).'?'; + $start = $this->map['p'][$this->currentPos]; + } else { + $to = $this->map['p'][$this->currentPos]; + } + } + $ret .= substr($this->datas, $start, $to - $start); + break; + } + + return $ret; + } + + /** + * @see Swift_CharacterStream::readBytes() + * + * @param int $length + * + * @return int[] + */ + public function readBytes($length) + { + $read = $this->read($length); + if (false !== $read) { + $ret = array_map('ord', str_split($read, 1)); + + return $ret; + } + + return false; + } + + /** + * @see Swift_CharacterStream::setPointer() + * + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($this->charCount < $charOffset) { + $charOffset = $this->charCount; + } + $this->currentPos = $charOffset; + } + + /** + * @see Swift_CharacterStream::write() + * + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->charReader)) { + $this->charReader = $this->charReaderFactory->getReaderFor( + $this->charset); + $this->map = []; + $this->mapType = $this->charReader->getMapType(); + } + $ignored = ''; + $this->datas .= $chars; + $this->charCount += $this->charReader->getCharPositions(substr($this->datas, $this->datasSize), $this->datasSize, $this->map, $ignored); + if (false !== $ignored) { + $this->datasSize = \strlen($this->datas) - \strlen($ignored); + } else { + $this->datasSize = \strlen($this->datas); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php new file mode 100644 index 0000000..a711bac --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Base class for Spools (implements time and message limits). + * + * @author Fabien Potencier + */ +abstract class Swift_ConfigurableSpool implements Swift_Spool +{ + /** The maximum number of messages to send per flush */ + private $message_limit; + + /** The time limit per flush */ + private $time_limit; + + /** + * Sets the maximum number of messages to send per flush. + * + * @param int $limit + */ + public function setMessageLimit($limit) + { + $this->message_limit = (int) $limit; + } + + /** + * Gets the maximum number of messages to send per flush. + * + * @return int The limit + */ + public function getMessageLimit() + { + return $this->message_limit; + } + + /** + * Sets the time limit (in seconds) per flush. + * + * @param int $limit The limit + */ + public function setTimeLimit($limit) + { + $this->time_limit = (int) $limit; + } + + /** + * Gets the time limit (in seconds) per flush. + * + * @return int The limit + */ + public function getTimeLimit() + { + return $this->time_limit; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php new file mode 100644 index 0000000..3cc885e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php @@ -0,0 +1,387 @@ +store); + } + + /** + * Test if an item is registered in this container with the given name. + * + * @see register() + * + * @param string $itemName + * + * @return bool + */ + public function has($itemName) + { + return \array_key_exists($itemName, $this->store) + && isset($this->store[$itemName]['lookupType']); + } + + /** + * Lookup the item with the given $itemName. + * + * @see register() + * + * @param string $itemName + * + * @return mixed + * + * @throws Swift_DependencyException If the dependency is not found + */ + public function lookup($itemName) + { + if (!$this->has($itemName)) { + throw new Swift_DependencyException('Cannot lookup dependency "'.$itemName.'" since it is not registered.'); + } + + switch ($this->store[$itemName]['lookupType']) { + case self::TYPE_ALIAS: + return $this->createAlias($itemName); + case self::TYPE_VALUE: + return $this->getValue($itemName); + case self::TYPE_INSTANCE: + return $this->createNewInstance($itemName); + case self::TYPE_SHARED: + return $this->createSharedInstance($itemName); + case self::TYPE_ARRAY: + return $this->createDependenciesFor($itemName); + } + } + + /** + * Create an array of arguments passed to the constructor of $itemName. + * + * @param string $itemName + * + * @return array + */ + public function createDependenciesFor($itemName) + { + $args = []; + if (isset($this->store[$itemName]['args'])) { + $args = $this->resolveArgs($this->store[$itemName]['args']); + } + + return $args; + } + + /** + * Register a new dependency with $itemName. + * + * This method returns the current DependencyContainer instance because it + * requires the use of the fluid interface to set the specific details for the + * dependency. + * + * @see asNewInstanceOf(), asSharedInstanceOf(), asValue() + * + * @param string $itemName + * + * @return $this + */ + public function register($itemName) + { + $this->store[$itemName] = []; + $this->endPoint = &$this->store[$itemName]; + + return $this; + } + + /** + * Specify the previously registered item as a literal value. + * + * {@link register()} must be called before this will work. + * + * @param mixed $value + * + * @return $this + */ + public function asValue($value) + { + $endPoint = &$this->getEndPoint(); + $endPoint['lookupType'] = self::TYPE_VALUE; + $endPoint['value'] = $value; + + return $this; + } + + /** + * Specify the previously registered item as an alias of another item. + * + * @param string $lookup + * + * @return $this + */ + public function asAliasOf($lookup) + { + $endPoint = &$this->getEndPoint(); + $endPoint['lookupType'] = self::TYPE_ALIAS; + $endPoint['ref'] = $lookup; + + return $this; + } + + /** + * Specify the previously registered item as a new instance of $className. + * + * {@link register()} must be called before this will work. + * Any arguments can be set with {@link withDependencies()}, + * {@link addConstructorValue()} or {@link addConstructorLookup()}. + * + * @see withDependencies(), addConstructorValue(), addConstructorLookup() + * + * @param string $className + * + * @return $this + */ + public function asNewInstanceOf($className) + { + $endPoint = &$this->getEndPoint(); + $endPoint['lookupType'] = self::TYPE_INSTANCE; + $endPoint['className'] = $className; + + return $this; + } + + /** + * Specify the previously registered item as a shared instance of $className. + * + * {@link register()} must be called before this will work. + * + * @param string $className + * + * @return $this + */ + public function asSharedInstanceOf($className) + { + $endPoint = &$this->getEndPoint(); + $endPoint['lookupType'] = self::TYPE_SHARED; + $endPoint['className'] = $className; + + return $this; + } + + /** + * Specify the previously registered item as array of dependencies. + * + * {@link register()} must be called before this will work. + * + * @return $this + */ + public function asArray() + { + $endPoint = &$this->getEndPoint(); + $endPoint['lookupType'] = self::TYPE_ARRAY; + + return $this; + } + + /** + * Specify a list of injected dependencies for the previously registered item. + * + * This method takes an array of lookup names. + * + * @see addConstructorValue(), addConstructorLookup() + * + * @return $this + */ + public function withDependencies(array $lookups) + { + $endPoint = &$this->getEndPoint(); + $endPoint['args'] = []; + foreach ($lookups as $lookup) { + $this->addConstructorLookup($lookup); + } + + return $this; + } + + /** + * Specify a literal (non looked up) value for the constructor of the + * previously registered item. + * + * @see withDependencies(), addConstructorLookup() + * + * @param mixed $value + * + * @return $this + */ + public function addConstructorValue($value) + { + $endPoint = &$this->getEndPoint(); + if (!isset($endPoint['args'])) { + $endPoint['args'] = []; + } + $endPoint['args'][] = ['type' => 'value', 'item' => $value]; + + return $this; + } + + /** + * Specify a dependency lookup for the constructor of the previously + * registered item. + * + * @see withDependencies(), addConstructorValue() + * + * @param string $lookup + * + * @return $this + */ + public function addConstructorLookup($lookup) + { + $endPoint = &$this->getEndPoint(); + if (!isset($this->endPoint['args'])) { + $endPoint['args'] = []; + } + $endPoint['args'][] = ['type' => 'lookup', 'item' => $lookup]; + + return $this; + } + + /** Get the literal value with $itemName */ + private function getValue($itemName) + { + return $this->store[$itemName]['value']; + } + + /** Resolve an alias to another item */ + private function createAlias($itemName) + { + return $this->lookup($this->store[$itemName]['ref']); + } + + /** Create a fresh instance of $itemName */ + private function createNewInstance($itemName) + { + $reflector = new ReflectionClass($this->store[$itemName]['className']); + if ($reflector->getConstructor()) { + return $reflector->newInstanceArgs( + $this->createDependenciesFor($itemName) + ); + } + + return $reflector->newInstance(); + } + + /** Create and register a shared instance of $itemName */ + private function createSharedInstance($itemName) + { + if (!isset($this->store[$itemName]['instance'])) { + $this->store[$itemName]['instance'] = $this->createNewInstance($itemName); + } + + return $this->store[$itemName]['instance']; + } + + /** Get the current endpoint in the store */ + private function &getEndPoint() + { + if (!isset($this->endPoint)) { + throw new BadMethodCallException('Component must first be registered by calling register()'); + } + + return $this->endPoint; + } + + /** Get an argument list with dependencies resolved */ + private function resolveArgs(array $args) + { + $resolved = []; + foreach ($args as $argDefinition) { + switch ($argDefinition['type']) { + case 'lookup': + $resolved[] = $this->lookupRecursive($argDefinition['item']); + break; + case 'value': + $resolved[] = $argDefinition['item']; + break; + } + } + + return $resolved; + } + + /** Resolve a single dependency with an collections */ + private function lookupRecursive($item) + { + if (\is_array($item)) { + $collection = []; + foreach ($item as $k => $v) { + $collection[$k] = $this->lookupRecursive($v); + } + + return $collection; + } + + return $this->lookup($item); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php new file mode 100644 index 0000000..799d38d --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php @@ -0,0 +1,27 @@ +createDependenciesFor('mime.embeddedfile') + ); + + $this->setBody($data); + $this->setFilename($filename); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new EmbeddedFile from a filesystem path. + * + * @param string $path + * + * @return Swift_Mime_EmbeddedFile + */ + public static function fromPath($path) + { + return (new self())->setFile(new Swift_ByteStream_FileByteStream($path)); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php new file mode 100644 index 0000000..2073abc --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php @@ -0,0 +1,28 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $encodedString = base64_encode($string); + $firstLine = ''; + + if (0 != $firstLineOffset) { + $firstLine = substr( + $encodedString, 0, $maxLineLength - $firstLineOffset + )."\r\n"; + $encodedString = substr( + $encodedString, $maxLineLength - $firstLineOffset + ); + } + + return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n")); + } + + /** + * Does nothing. + */ + public function charsetChanged($charset) + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php new file mode 100644 index 0000000..f078d6d --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php @@ -0,0 +1,300 @@ + '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04', + 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09', + 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E', + 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13', + 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18', + 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D', + 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22', + 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27', + 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C', + 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31', + 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36', + 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B', + 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40', + 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45', + 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A', + 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F', + 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54', + 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59', + 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E', + 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63', + 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68', + 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D', + 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72', + 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77', + 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C', + 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81', + 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86', + 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B', + 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90', + 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95', + 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A', + 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F', + 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4', + 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9', + 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE', + 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3', + 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8', + 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD', + 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2', + 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7', + 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC', + 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1', + 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6', + 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB', + 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0', + 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5', + 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA', + 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF', + 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4', + 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9', + 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE', + 255 => '=FF', + ]; + + protected static $safeMapShare = []; + + /** + * A map of non-encoded ascii characters. + * + * @var string[] + */ + protected $safeMap = []; + + /** + * Creates a new QpEncoder for the given CharacterStream. + * + * @param Swift_CharacterStream $charStream to use for reading characters + * @param Swift_StreamFilter $filter if input should be canonicalized + */ + public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null) + { + $this->charStream = $charStream; + if (!isset(self::$safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$safeMapShare[$this->getSafeMapShareId()] = $this->safeMap; + } else { + $this->safeMap = self::$safeMapShare[$this->getSafeMapShareId()]; + } + $this->filter = $filter; + } + + public function __sleep() + { + return ['charStream', 'filter']; + } + + public function __wakeup() + { + if (!isset(self::$safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$safeMapShare[$this->getSafeMapShareId()] = $this->safeMap; + } else { + $this->safeMap = self::$safeMapShare[$this->getSafeMapShareId()]; + } + } + + protected function getSafeMapShareId() + { + return static::class; + } + + protected function initSafeMap() + { + foreach (array_merge( + [0x09, 0x20], range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) { + $this->safeMap[$byte] = \chr($byte); + } + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + * + * @param string $string to encode + * @param int $firstLineOffset optional + * @param int $maxLineLength optional 0 indicates the default of 76 chars + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $lines = []; + $lNo = 0; + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $size = $lineLen = 0; + + $this->charStream->flushContents(); + $this->charStream->importString($string); + + // Fetching more than 4 chars at one is slower, as is fetching fewer bytes + // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6 + // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes + while (false !== $bytes = $this->nextSequence()) { + // If we're filtering the input + if (isset($this->filter)) { + // If we can't filter because we need more bytes + while ($this->filter->shouldBuffer($bytes)) { + // Then collect bytes into the buffer + if (false === $moreBytes = $this->nextSequence(1)) { + break; + } + + foreach ($moreBytes as $b) { + $bytes[] = $b; + } + } + // And filter them + $bytes = $this->filter->filter($bytes); + } + + $enc = $this->encodeByteSequence($bytes, $size); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + (false === $i ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $lines[$lNo] = ''; + $currentLine = &$lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if (false === $i) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + + return $this->standardize(implode("=\r\n", $lines)); + } + + /** + * Updates the charset used. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charStream->setCharacterSet($charset); + } + + /** + * Encode the given byte array into a verbatim QP form. + * + * @param int[] $bytes + * @param int $size + * + * @return string + */ + protected function encodeByteSequence(array $bytes, &$size) + { + $ret = ''; + $size = 0; + foreach ($bytes as $b) { + if (isset($this->safeMap[$b])) { + $ret .= $this->safeMap[$b]; + ++$size; + } else { + $ret .= self::$qpMap[$b]; + $size += 3; + } + } + + return $ret; + } + + /** + * Get the next sequence of bytes to read from the char stream. + * + * @param int $size number of bytes to read + * + * @return int[] + */ + protected function nextSequence($size = 4) + { + return $this->charStream->readBytes($size); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * + * @param string $string + * + * @return string + */ + protected function standardize($string) + { + $string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], + ["=09\r\n", "=20\r\n", "\r\n"], $string + ); + switch ($end = \ord(substr($string, -1))) { + case 0x09: + case 0x20: + $string = substr_replace($string, self::$qpMap[$end], -1); + } + + return $string; + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->charStream = clone $this->charStream; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php new file mode 100644 index 0000000..7eac368 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php @@ -0,0 +1,90 @@ +charStream = $charStream; + } + + /** + * Takes an unencoded string and produces a string encoded according to + * RFC 2231 from it. + * + * @param string $string + * @param int $firstLineOffset + * @param int $maxLineLength optional, 0 indicates the default of 75 bytes + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + $lines = []; + $lineCount = 0; + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + + if (0 >= $maxLineLength) { + $maxLineLength = 75; + } + + $this->charStream->flushContents(); + $this->charStream->importString($string); + + $thisLineLength = $maxLineLength - $firstLineOffset; + + while (false !== $char = $this->charStream->read(4)) { + $encodedChar = rawurlencode($char); + if (0 != \strlen($currentLine) + && \strlen($currentLine.$encodedChar) > $thisLineLength) { + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + $thisLineLength = $maxLineLength; + } + $currentLine .= $encodedChar; + } + + return implode("\r\n", $lines); + } + + /** + * Updates the charset used. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charStream->setCharacterSet($charset); + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->charStream = clone $this->charStream; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php new file mode 100644 index 0000000..18994c1 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php @@ -0,0 +1,64 @@ +command = $command; + $this->successCodes = $successCodes; + } + + /** + * Get the command which was sent to the server. + * + * @return string + */ + public function getCommand() + { + return $this->command; + } + + /** + * Get the numeric response codes which indicate success for this command. + * + * @return int[] + */ + public function getSuccessCodes() + { + return $this->successCodes; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php new file mode 100644 index 0000000..b158eab --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php @@ -0,0 +1,22 @@ +source = $source; + } + + /** + * Get the source object of this event. + * + * @return object + */ + public function getSource() + { + return $this->source; + } + + /** + * Prevent this Event from bubbling any further up the stack. + */ + public function cancelBubble($cancel = true) + { + $this->bubbleCancelled = $cancel; + } + + /** + * Returns true if this Event will not bubble any further up the stack. + * + * @return bool + */ + public function bubbleCancelled() + { + return $this->bubbleCancelled; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php new file mode 100644 index 0000000..ff7c371 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php @@ -0,0 +1,64 @@ +response = $response; + $this->valid = $valid; + } + + /** + * Get the response which was received from the server. + * + * @return string + */ + public function getResponse() + { + return $this->response; + } + + /** + * Get the success status of this Event. + * + * @return bool + */ + public function isValid() + { + return $this->valid; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php new file mode 100644 index 0000000..85115a3 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php @@ -0,0 +1,22 @@ +message = $message; + $this->result = self::RESULT_PENDING; + } + + /** + * Get the Transport used to send the Message. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->getSource(); + } + + /** + * Get the Message being sent. + * + * @return Swift_Mime_SimpleMessage + */ + public function getMessage() + { + return $this->message; + } + + /** + * Set the array of addresses that failed in sending. + * + * @param array $recipients + */ + public function setFailedRecipients($recipients) + { + $this->failedRecipients = $recipients; + } + + /** + * Get an recipient addresses which were not accepted for delivery. + * + * @return string[] + */ + public function getFailedRecipients() + { + return $this->failedRecipients; + } + + /** + * Set the result of sending. + * + * @param int $result + */ + public function setResult($result) + { + $this->result = $result; + } + + /** + * Get the result of this Event. + * + * The return value is a bitmask from + * {@see RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED} + * + * @return int + */ + public function getResult() + { + return $this->result; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php new file mode 100644 index 0000000..f7bf55e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php @@ -0,0 +1,27 @@ +eventMap = [ + 'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener', + 'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener', + 'Swift_Events_SendEvent' => 'Swift_Events_SendListener', + 'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener', + 'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener', + ]; + } + + /** + * Create a new SendEvent for $source and $message. + * + * @return Swift_Events_SendEvent + */ + public function createSendEvent(Swift_Transport $source, Swift_Mime_SimpleMessage $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + /** + * Create a new CommandEvent for $source and $command. + * + * @param string $command That will be executed + * @param array $successCodes That are needed + * + * @return Swift_Events_CommandEvent + */ + public function createCommandEvent(Swift_Transport $source, $command, $successCodes = []) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + /** + * Create a new ResponseEvent for $source and $response. + * + * @param string $response + * @param bool $valid If the response is valid + * + * @return Swift_Events_ResponseEvent + */ + public function createResponseEvent(Swift_Transport $source, $response, $valid) + { + return new Swift_Events_ResponseEvent($source, $response, $valid); + } + + /** + * Create a new TransportChangeEvent for $source. + * + * @return Swift_Events_TransportChangeEvent + */ + public function createTransportChangeEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + /** + * Create a new TransportExceptionEvent for $source. + * + * @return Swift_Events_TransportExceptionEvent + */ + public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($source, $ex); + } + + /** + * Bind an event listener to this dispatcher. + */ + public function bindEventListener(Swift_Events_EventListener $listener) + { + foreach ($this->listeners as $l) { + // Already loaded + if ($l === $listener) { + return; + } + } + $this->listeners[] = $listener; + } + + /** + * Dispatch the given Event to all suitable listeners. + * + * @param string $target method + */ + public function dispatchEvent(Swift_Events_EventObject $evt, $target) + { + $bubbleQueue = $this->prepareBubbleQueue($evt); + $this->bubble($bubbleQueue, $evt, $target); + } + + /** Queue listeners on a stack ready for $evt to be bubbled up it */ + private function prepareBubbleQueue(Swift_Events_EventObject $evt) + { + $bubbleQueue = []; + $evtClass = \get_class($evt); + foreach ($this->listeners as $listener) { + if (\array_key_exists($evtClass, $this->eventMap) + && ($listener instanceof $this->eventMap[$evtClass])) { + $bubbleQueue[] = $listener; + } + } + + return $bubbleQueue; + } + + /** Bubble $evt up the stack calling $target() on each listener */ + private function bubble(array &$bubbleQueue, Swift_Events_EventObject $evt, $target) + { + if (!$evt->bubbleCancelled() && $listener = array_shift($bubbleQueue)) { + $listener->$target($evt); + $this->bubble($bubbleQueue, $evt, $target); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php new file mode 100644 index 0000000..a8972fd --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php @@ -0,0 +1,27 @@ +getSource(); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php new file mode 100644 index 0000000..4a7492b --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php @@ -0,0 +1,37 @@ +exception = $ex; + } + + /** + * Get the TransportException thrown. + * + * @return Swift_TransportException + */ + public function getException() + { + return $this->exception; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php new file mode 100644 index 0000000..ad80eb0 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php @@ -0,0 +1,22 @@ +createDependenciesFor('transport.failover') + ); + + $this->setTransports($transports); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php new file mode 100644 index 0000000..7af8471 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages on the filesystem. + * + * @author Fabien Potencier + * @author Xavier De Cock + */ +class Swift_FileSpool extends Swift_ConfigurableSpool +{ + /** The spool directory */ + private $path; + + /** + * File WriteRetry Limit. + * + * @var int + */ + private $retryLimit = 10; + + /** + * Create a new FileSpool. + * + * @param string $path + * + * @throws Swift_IoException + */ + public function __construct($path) + { + $this->path = $path; + + if (!file_exists($this->path)) { + if (!mkdir($this->path, 0777, true)) { + throw new Swift_IoException(sprintf('Unable to create path "%s".', $this->path)); + } + } + } + + /** + * Tests if this Spool mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Spool mechanism. + */ + public function start() + { + } + + /** + * Stops this Spool mechanism. + */ + public function stop() + { + } + + /** + * Allow to manage the enqueuing retry limit. + * + * Default, is ten and allows over 64^20 different fileNames + * + * @param int $limit + */ + public function setRetryLimit($limit) + { + $this->retryLimit = $limit; + } + + /** + * Queues a message. + * + * @param Swift_Mime_SimpleMessage $message The message to store + * + * @throws Swift_IoException + * + * @return bool + */ + public function queueMessage(Swift_Mime_SimpleMessage $message) + { + $ser = serialize($message); + $fileName = $this->path.'/'.$this->getRandomString(10); + for ($i = 0; $i < $this->retryLimit; ++$i) { + /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */ + $fp = @fopen($fileName.'.message', 'xb'); + if (false !== $fp) { + if (false === fwrite($fp, $ser)) { + return false; + } + + return fclose($fp); + } else { + /* The file already exists, we try a longer fileName */ + $fileName .= $this->getRandomString(1); + } + } + + throw new Swift_IoException(sprintf('Unable to create a file for enqueuing Message in "%s".', $this->path)); + } + + /** + * Execute a recovery if for any reason a process is sending for too long. + * + * @param int $timeout in second Defaults is for very slow smtp responses + */ + public function recover($timeout = 900) + { + foreach (new DirectoryIterator($this->path) as $file) { + $file = $file->getRealPath(); + + if ('.message.sending' == substr($file, -16)) { + $lockedtime = filectime($file); + if ((time() - $lockedtime) > $timeout) { + rename($file, substr($file, 0, -8)); + } + } + } + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent e-mail's + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + $directoryIterator = new DirectoryIterator($this->path); + + /* Start the transport only if there are queued files to send */ + if (!$transport->isStarted()) { + foreach ($directoryIterator as $file) { + if ('.message' == substr($file->getRealPath(), -8)) { + $transport->start(); + break; + } + } + } + + $failedRecipients = (array) $failedRecipients; + $count = 0; + $time = time(); + foreach ($directoryIterator as $file) { + $file = $file->getRealPath(); + + if ('.message' != substr($file, -8)) { + continue; + } + + /* We try a rename, it's an atomic operation, and avoid locking the file */ + if (rename($file, $file.'.sending')) { + $message = unserialize(file_get_contents($file.'.sending')); + + $count += $transport->send($message, $failedRecipients); + + unlink($file.'.sending'); + } else { + /* This message has just been catched by another process */ + continue; + } + + if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) { + break; + } + + if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) { + break; + } + } + + return $count; + } + + /** + * Returns a random string needed to generate a fileName for the queue. + * + * @param int $count + * + * @return string + */ + protected function getRandomString($count) + { + // This string MUST stay FS safe, avoid special chars + $base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'; + $ret = ''; + $strlen = \strlen($base); + for ($i = 0; $i < $count; ++$i) { + $ret .= $base[random_int(0, $strlen - 1)]; + } + + return $ret; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php new file mode 100644 index 0000000..0b24db1 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php @@ -0,0 +1,24 @@ +setFile(new Swift_ByteStream_FileByteStream($path)); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php new file mode 100644 index 0000000..379a5a1 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php @@ -0,0 +1,75 @@ +stream = $stream; + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->contents[$nsKey][$itemKey] = $string; + break; + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->contents[$nsKey][$itemKey] = ''; + } + $this->contents[$nsKey][$itemKey] .= $string; + break; + default: + throw new Swift_SwiftException('Invalid mode ['.$mode.'] used to set nsKey='.$nsKey.', itemKey='.$itemKey); + } + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param int $mode + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->clearKey($nsKey, $itemKey); + // no break + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->contents[$nsKey][$itemKey] = ''; + } + while (false !== $bytes = $os->read(8192)) { + $this->contents[$nsKey][$itemKey] .= $bytes; + } + break; + default: + throw new Swift_SwiftException('Invalid mode ['.$mode.'] used to set nsKey='.$nsKey.', itemKey='.$itemKey); + } + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @return string + */ + public function getString($nsKey, $itemKey) + { + $this->prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + return $this->contents[$nsKey][$itemKey]; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + $this->prepareCache($nsKey); + $is->write($this->getString($nsKey, $itemKey)); + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return bool + */ + public function hasKey($nsKey, $itemKey) + { + $this->prepareCache($nsKey); + + return \array_key_exists($itemKey, $this->contents[$nsKey]); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + unset($this->contents[$nsKey][$itemKey]); + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + unset($this->contents[$nsKey]); + } + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function prepareCache($nsKey) + { + if (!\array_key_exists($nsKey, $this->contents)) { + $this->contents[$nsKey] = []; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php new file mode 100644 index 0000000..33b6367 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php @@ -0,0 +1,294 @@ +stream = $stream; + $this->path = $path; + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + * + * @throws Swift_IoException + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException('Invalid mode ['.$mode.'] used to set nsKey='.$nsKey.', itemKey='.$itemKey); + break; + } + fwrite($fp, $string); + $this->freeHandle($nsKey, $itemKey); + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param int $mode + * + * @throws Swift_IoException + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException('Invalid mode ['.$mode.'] used to set nsKey='.$nsKey.', itemKey='.$itemKey); + break; + } + while (false !== $bytes = $os->read(8192)) { + fwrite($fp, $bytes); + } + $this->freeHandle($nsKey, $itemKey); + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @throws Swift_IoException + * + * @return string + */ + public function getString($nsKey, $itemKey) + { + $this->prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_START); + $str = ''; + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $str .= $bytes; + } + $this->freeHandle($nsKey, $itemKey); + + return $str; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_START); + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $is->write($bytes); + } + $this->freeHandle($nsKey, $itemKey); + } + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return bool + */ + public function hasKey($nsKey, $itemKey) + { + return is_file($this->path.'/'.$nsKey.'/'.$itemKey); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + if ($this->hasKey($nsKey, $itemKey)) { + $this->freeHandle($nsKey, $itemKey); + unlink($this->path.'/'.$nsKey.'/'.$itemKey); + } + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + if (\array_key_exists($nsKey, $this->keys)) { + foreach ($this->keys[$nsKey] as $itemKey => $null) { + $this->clearKey($nsKey, $itemKey); + } + if (is_dir($this->path.'/'.$nsKey)) { + rmdir($this->path.'/'.$nsKey); + } + unset($this->keys[$nsKey]); + } + } + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function prepareCache($nsKey) + { + $cacheDir = $this->path.'/'.$nsKey; + if (!is_dir($cacheDir)) { + if (!mkdir($cacheDir)) { + throw new Swift_IoException('Failed to create cache directory '.$cacheDir); + } + $this->keys[$nsKey] = []; + } + } + + /** + * Get a file handle on the cache item. + * + * @param string $nsKey + * @param string $itemKey + * @param int $position + * + * @return resource + */ + private function getHandle($nsKey, $itemKey, $position) + { + if (!isset($this->keys[$nsKey][$itemKey])) { + $openMode = $this->hasKey($nsKey, $itemKey) ? 'r+b' : 'w+b'; + $fp = fopen($this->path.'/'.$nsKey.'/'.$itemKey, $openMode); + $this->keys[$nsKey][$itemKey] = $fp; + } + if (self::POSITION_START == $position) { + fseek($this->keys[$nsKey][$itemKey], 0, SEEK_SET); + } elseif (self::POSITION_END == $position) { + fseek($this->keys[$nsKey][$itemKey], 0, SEEK_END); + } + + return $this->keys[$nsKey][$itemKey]; + } + + private function freeHandle($nsKey, $itemKey) + { + $fp = $this->getHandle($nsKey, $itemKey, self::POSITION_CURRENT); + fclose($fp); + $this->keys[$nsKey][$itemKey] = null; + } + + /** + * Destructor. + */ + public function __destruct() + { + foreach ($this->keys as $nsKey => $null) { + $this->clearAll($nsKey); + } + } + + public function __wakeup() + { + $this->keys = []; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php new file mode 100644 index 0000000..be2dbba --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php @@ -0,0 +1,47 @@ +keyCache = $keyCache; + } + + /** + * Specify a stream to write through for each write(). + */ + public function setWriteThroughStream(Swift_InputByteStream $is) + { + $this->writeThrough = $is; + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + * @param Swift_InputByteStream $is optional + */ + public function write($bytes, Swift_InputByteStream $is = null) + { + $this->keyCache->setString( + $this->nsKey, $this->itemKey, $bytes, Swift_KeyCache::MODE_APPEND + ); + if (isset($is)) { + $is->write($bytes); + } + if (isset($this->writeThrough)) { + $this->writeThrough->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Not used. + */ + public function bind(Swift_InputByteStream $is) + { + } + + /** + * Not used. + */ + public function unbind(Swift_InputByteStream $is) + { + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->keyCache->clearKey($this->nsKey, $this->itemKey); + } + + /** + * Set the nsKey which will be written to. + * + * @param string $nsKey + */ + public function setNsKey($nsKey) + { + $this->nsKey = $nsKey; + } + + /** + * Set the itemKey which will be written to. + * + * @param string $itemKey + */ + public function setItemKey($itemKey) + { + $this->itemKey = $itemKey; + } + + /** + * Any implementation should be cloneable, allowing the clone to access a + * separate $nsKey and $itemKey. + */ + public function __clone() + { + $this->writeThrough = null; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php new file mode 100644 index 0000000..244b5f6 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php @@ -0,0 +1,33 @@ +createDependenciesFor('transport.loadbalanced') + ); + + $this->setTransports($transports); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php new file mode 100644 index 0000000..5763007 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php @@ -0,0 +1,98 @@ +transport = $transport; + } + + /** + * Create a new class instance of one of the message services. + * + * For example 'mimepart' would create a 'message.mimepart' instance + * + * @param string $service + * + * @return object + */ + public function createMessage($service = 'message') + { + return Swift_DependencyContainer::getInstance() + ->lookup('message.'.$service); + } + + /** + * Send the given Message like it would be sent in a mail client. + * + * All recipients (with the exception of Bcc) will be able to see the other + * recipients this message was sent to. + * + * Recipient/sender data will be retrieved from the Message object. + * + * The return value is the number of recipients who were accepted for + * delivery. + * + * @param array $failedRecipients An array of failures by-reference + * + * @return int The number of successful recipients. Can be 0 which indicates failure + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + // FIXME: to be removed in 7.0 (as transport must now start itself on send) + if (!$this->transport->isStarted()) { + $this->transport->start(); + } + + $sent = 0; + + try { + $sent = $this->transport->send($message, $failedRecipients); + } catch (Swift_RfcComplianceException $e) { + foreach ($message->getTo() as $address => $name) { + $failedRecipients[] = $address; + } + } + + return $sent; + } + + /** + * Register a plugin using a known unique key (e.g. myPlugin). + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->transport->registerPlugin($plugin); + } + + /** + * The Transport used to send messages. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->transport; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php new file mode 100644 index 0000000..19aa82a --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php @@ -0,0 +1,53 @@ +recipients = $recipients; + } + + /** + * Returns true only if there are more recipients to send to. + * + * @return bool + */ + public function hasNext() + { + return !empty($this->recipients); + } + + /** + * Returns an array where the keys are the addresses of recipients and the + * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL). + * + * @return array + */ + public function nextRecipient() + { + return array_splice($this->recipients, 0, 1); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php new file mode 100644 index 0000000..650f3ec --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php @@ -0,0 +1,32 @@ + 'Foo') or ('foo@bar' => NULL). + * + * @return array + */ + public function nextRecipient(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php new file mode 100644 index 0000000..e3b0894 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in memory. + * + * @author Fabien Potencier + */ +class Swift_MemorySpool implements Swift_Spool +{ + protected $messages = []; + private $flushRetries = 3; + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * @param int $retries + */ + public function setFlushRetries($retries) + { + $this->flushRetries = $retries; + } + + /** + * Stores a message in the queue. + * + * @param Swift_Mime_SimpleMessage $message The message to store + * + * @return bool Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_SimpleMessage $message) + { + //clone the message to make sure it is not changed while in the queue + $this->messages[] = clone $message; + + return true; + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + if (!$this->messages) { + return 0; + } + + if (!$transport->isStarted()) { + $transport->start(); + } + + $count = 0; + $retries = $this->flushRetries; + while ($retries--) { + try { + while ($message = array_pop($this->messages)) { + $count += $transport->send($message, $failedRecipients); + } + } catch (Swift_TransportException $exception) { + if ($retries) { + // re-queue the message at the end of the queue to give a chance + // to the other messages to be sent, in case the failure was due to + // this message and not just the transport failing + array_unshift($this->messages, $message); + + // wait half a second before we try again + usleep(500000); + } else { + throw $exception; + } + } + } + + return $count; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php new file mode 100644 index 0000000..819ffc3 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php @@ -0,0 +1,279 @@ +createDependenciesFor('mime.message') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setSubject($subject); + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Add a MimePart to this Message. + * + * @param string|Swift_OutputByteStream $body + * @param string $contentType + * @param string $charset + * + * @return $this + */ + public function addPart($body, $contentType = null, $charset = null) + { + return $this->attach((new Swift_MimePart($body, $contentType, $charset))->setEncoder($this->getEncoder())); + } + + /** + * Attach a new signature handler to the message. + * + * @return $this + */ + public function attachSigner(Swift_Signer $signer) + { + if ($signer instanceof Swift_Signers_HeaderSigner) { + $this->headerSigners[] = $signer; + } elseif ($signer instanceof Swift_Signers_BodySigner) { + $this->bodySigners[] = $signer; + } + + return $this; + } + + /** + * Detach a signature handler from a message. + * + * @return $this + */ + public function detachSigner(Swift_Signer $signer) + { + if ($signer instanceof Swift_Signers_HeaderSigner) { + foreach ($this->headerSigners as $k => $headerSigner) { + if ($headerSigner === $signer) { + unset($this->headerSigners[$k]); + + return $this; + } + } + } elseif ($signer instanceof Swift_Signers_BodySigner) { + foreach ($this->bodySigners as $k => $bodySigner) { + if ($bodySigner === $signer) { + unset($this->bodySigners[$k]); + + return $this; + } + } + } + + return $this; + } + + /** + * Clear all signature handlers attached to the message. + * + * @return $this + */ + public function clearSigners() + { + $this->headerSigners = []; + $this->bodySigners = []; + + return $this; + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + if (empty($this->headerSigners) && empty($this->bodySigners)) { + return parent::toString(); + } + + $this->saveMessage(); + + $this->doSign(); + + $string = parent::toString(); + + $this->restoreMessage(); + + return $string; + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (empty($this->headerSigners) && empty($this->bodySigners)) { + parent::toByteStream($is); + + return; + } + + $this->saveMessage(); + + $this->doSign(); + + parent::toByteStream($is); + + $this->restoreMessage(); + } + + public function __wakeup() + { + Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message'); + } + + /** + * loops through signers and apply the signatures. + */ + protected function doSign() + { + foreach ($this->bodySigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->signMessage($this); + } + + foreach ($this->headerSigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->reset(); + + $signer->setHeaders($this->getHeaders()); + + $signer->startBody(); + $this->bodyToByteStream($signer); + $signer->endBody(); + + $signer->addSignature($this->getHeaders()); + } + } + + /** + * save the message before any signature is applied. + */ + protected function saveMessage() + { + $this->savedMessage = ['headers' => []]; + $this->savedMessage['body'] = $this->getBody(); + $this->savedMessage['children'] = $this->getChildren(); + if (\count($this->savedMessage['children']) > 0 && '' != $this->getBody()) { + $this->setChildren(array_merge([$this->becomeMimePart()], $this->savedMessage['children'])); + $this->setBody(''); + } + } + + /** + * save the original headers. + */ + protected function saveHeaders(array $altered) + { + foreach ($altered as $head) { + $lc = strtolower($head); + + if (!isset($this->savedMessage['headers'][$lc])) { + $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head); + } + } + } + + /** + * Remove or restore altered headers. + */ + protected function restoreHeaders() + { + foreach ($this->savedMessage['headers'] as $name => $savedValue) { + $headers = $this->getHeaders()->getAll($name); + + foreach ($headers as $key => $value) { + if (!isset($savedValue[$key])) { + $this->getHeaders()->remove($name, $key); + } + } + } + } + + /** + * Restore message body. + */ + protected function restoreMessage() + { + $this->setBody($this->savedMessage['body']); + $this->setChildren($this->savedMessage['children']); + + $this->restoreHeaders(); + $this->savedMessage = []; + } + + /** + * Clone Message Signers. + * + * @see Swift_Mime_SimpleMimeEntity::__clone() + */ + public function __clone() + { + parent::__clone(); + foreach ($this->bodySigners as $key => $bodySigner) { + $this->bodySigners[$key] = clone $bodySigner; + } + + foreach ($this->headerSigners as $key => $headerSigner) { + $this->headerSigners[$key] = clone $headerSigner; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php new file mode 100644 index 0000000..d994373 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php @@ -0,0 +1,144 @@ +setDisposition('attachment'); + $this->setContentType('application/octet-stream'); + $this->mimeTypes = $mimeTypes; + } + + /** + * Get the nesting level used for this attachment. + * + * Always returns {@link LEVEL_MIXED}. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_MIXED; + } + + /** + * Get the Content-Disposition of this attachment. + * + * By default attachments have a disposition of "attachment". + * + * @return string + */ + public function getDisposition() + { + return $this->getHeaderFieldModel('Content-Disposition'); + } + + /** + * Set the Content-Disposition of this attachment. + * + * @param string $disposition + * + * @return $this + */ + public function setDisposition($disposition) + { + if (!$this->setHeaderFieldModel('Content-Disposition', $disposition)) { + $this->getHeaders()->addParameterizedHeader('Content-Disposition', $disposition); + } + + return $this; + } + + /** + * Get the filename of this attachment when downloaded. + * + * @return string + */ + public function getFilename() + { + return $this->getHeaderParameter('Content-Disposition', 'filename'); + } + + /** + * Set the filename of this attachment. + * + * @param string $filename + * + * @return $this + */ + public function setFilename($filename) + { + $this->setHeaderParameter('Content-Disposition', 'filename', $filename); + $this->setHeaderParameter('Content-Type', 'name', $filename); + + return $this; + } + + /** + * Get the file size of this attachment. + * + * @return int + */ + public function getSize() + { + return $this->getHeaderParameter('Content-Disposition', 'size'); + } + + /** + * Set the file size of this attachment. + * + * @param int $size + * + * @return $this + */ + public function setSize($size) + { + $this->setHeaderParameter('Content-Disposition', 'size', $size); + + return $this; + } + + /** + * Set the file that this attachment is for. + * + * @param string $contentType optional + * + * @return $this + */ + public function setFile(Swift_FileStream $file, $contentType = null) + { + $this->setFilename(basename($file->getPath())); + $this->setBody($file, $contentType); + if (!isset($contentType)) { + $extension = strtolower(substr($file->getPath(), strrpos($file->getPath(), '.') + 1)); + + if (\array_key_exists($extension, $this->mimeTypes)) { + $this->setContentType($this->mimeTypes[$extension]); + } + } + + return $this; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php new file mode 100644 index 0000000..b49c3a8 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php @@ -0,0 +1,24 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $remainder = 0; + $base64ReadBufferRemainderBytes = null; + + // To reduce memory usage, the output buffer is streamed to the input buffer like so: + // Output Stream => base64encode => wrap line length => Input Stream + // HOWEVER it's important to note that base64_encode() should only be passed whole triplets of data (except for the final chunk of data) + // otherwise it will assume the input data has *ended* and it will incorrectly pad/terminate the base64 data mid-stream. + // We use $base64ReadBufferRemainderBytes to carry over 1-2 "remainder" bytes from the each chunk from OutputStream and pre-pend those onto the + // chunk of bytes read in the next iteration. + // When the OutputStream is empty, we must flush any remainder bytes. + while (true) { + $readBytes = $os->read(8192); + $atEOF = (false === $readBytes); + + if ($atEOF) { + $streamTheseBytes = $base64ReadBufferRemainderBytes; + } else { + $streamTheseBytes = $base64ReadBufferRemainderBytes.$readBytes; + } + $base64ReadBufferRemainderBytes = null; + $bytesLength = \strlen($streamTheseBytes); + + if (0 === $bytesLength) { // no data left to encode + break; + } + + // if we're not on the last block of the ouput stream, make sure $streamTheseBytes ends with a complete triplet of data + // and carry over remainder 1-2 bytes to the next loop iteration + if (!$atEOF) { + $excessBytes = $bytesLength % 3; + if (0 !== $excessBytes) { + $base64ReadBufferRemainderBytes = substr($streamTheseBytes, -$excessBytes); + $streamTheseBytes = substr($streamTheseBytes, 0, $bytesLength - $excessBytes); + } + } + + $encoded = base64_encode($streamTheseBytes); + $encodedTransformed = ''; + $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset; + + while ($thisMaxLineLength < \strlen($encoded)) { + $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength)."\r\n"; + $firstLineOffset = 0; + $encoded = substr($encoded, $thisMaxLineLength); + $thisMaxLineLength = $maxLineLength; + $remainder = 0; + } + + if (0 < $remainingLength = \strlen($encoded)) { + $remainder += $remainingLength; + $encodedTransformed .= $encoded; + $encoded = null; + } + + $is->write($encodedTransformed); + + if ($atEOF) { + break; + } + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'base64'. + * + * @return string + */ + public function getName() + { + return 'base64'; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php new file mode 100644 index 0000000..8dfea60 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php @@ -0,0 +1,121 @@ +charset = $charset ?: 'utf-8'; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + } + + /** + * Encode $in to $out. + * + * @param Swift_OutputByteStream $os to read from + * @param Swift_InputByteStream $is to write to + * @param int $firstLineOffset + * @param int $maxLineLength 0 indicates the default length for this encoding + * + * @throws RuntimeException + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ('utf-8' !== $this->charset) { + throw new RuntimeException(sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + $string = ''; + + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + $is->write($this->encodeString($string)); + } + + /** + * Get the MIME name of this content encoding scheme. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset if first line needs to be shorter + * @param int $maxLineLength 0 indicates the default length for this encoding + * + * @throws RuntimeException + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ('utf-8' !== $this->charset) { + throw new RuntimeException(sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + return $this->standardize(quoted_printable_encode($string)); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * + * @param string $string + * + * @return string + */ + protected function standardize($string) + { + // transform CR or LF to CRLF + $string = preg_replace('~=0D(?!=0A)|(? + */ +class Swift_Mime_ContentEncoder_NullContentEncoder implements Swift_Mime_ContentEncoder +{ + /** + * The name of this encoding scheme (probably 7bit or 8bit). + * + * @var string + */ + private $name; + + /** + * Creates a new NullContentEncoder with $name (probably 7bit or 8bit). + * + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $string; + } + + /** + * Encode stream $in to stream $out. + * + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + while (false !== ($bytes = $os->read(8192))) { + $is->write($bytes); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php new file mode 100644 index 0000000..72592fc --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php @@ -0,0 +1,164 @@ +name = $name; + $this->canonical = $canonical; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength - 0 means no wrapping will occur + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->canonical) { + $string = $this->canonicalize($string); + } + + return $this->safeWordwrap($string, $maxLineLength, "\r\n"); + } + + /** + * Encode stream $in to stream $out. + * + * @param int $firstLineOffset ignored + * @param int $maxLineLength optional, 0 means no wrapping will occur + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $leftOver = ''; + while (false !== $bytes = $os->read(8192)) { + $toencode = $leftOver.$bytes; + if ($this->canonical) { + $toencode = $this->canonicalize($toencode); + } + $wrapped = $this->safeWordwrap($toencode, $maxLineLength, "\r\n"); + $lastLinePos = strrpos($wrapped, "\r\n"); + $leftOver = substr($wrapped, $lastLinePos); + $wrapped = substr($wrapped, 0, $lastLinePos); + + $is->write($wrapped); + } + if (\strlen($leftOver)) { + $is->write($leftOver); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } + + /** + * A safer (but weaker) wordwrap for unicode. + * + * @param string $string + * @param int $length + * @param string $le + * + * @return string + */ + private function safeWordwrap($string, $length = 75, $le = "\r\n") + { + if (0 >= $length) { + return $string; + } + + $originalLines = explode($le, $string); + + $lines = []; + $lineCount = 0; + + foreach ($originalLines as $originalLine) { + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + + //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine); + $chunks = preg_split('/(?<=\s)/', $originalLine); + + foreach ($chunks as $chunk) { + if (0 != \strlen($currentLine) + && \strlen($currentLine.$chunk) > $length) { + $lines[] = ''; + $currentLine = &$lines[$lineCount++]; + } + $currentLine .= $chunk; + } + } + + return implode("\r\n", $lines); + } + + /** + * Canonicalize string input (fix CRLF). + * + * @param string $string + * + * @return string + */ + private function canonicalize($string) + { + return str_replace( + ["\r\n", "\r", "\n"], + ["\n", "\n", "\r\n"], + $string + ); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php new file mode 100644 index 0000000..465ffd8 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php @@ -0,0 +1,134 @@ +dotEscape = $dotEscape; + parent::__construct($charStream, $filter); + } + + public function __sleep() + { + return ['charStream', 'filter', 'dotEscape']; + } + + protected function getSafeMapShareId() + { + return static::class.($this->dotEscape ? '.dotEscape' : ''); + } + + protected function initSafeMap() + { + parent::initSafeMap(); + if ($this->dotEscape) { + /* Encode . as =2e for buggy remote servers */ + unset($this->safeMap[0x2e]); + } + } + + /** + * Encode stream $in to stream $out. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + * + * @param Swift_OutputByteStream $os output stream + * @param Swift_InputByteStream $is input stream + * @param int $firstLineOffset + * @param int $maxLineLength + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $this->charStream->flushContents(); + $this->charStream->importByteStream($os); + + $currentLine = ''; + $prepend = ''; + $size = $lineLen = 0; + + while (false !== $bytes = $this->nextSequence()) { + // If we're filtering the input + if (isset($this->filter)) { + // If we can't filter because we need more bytes + while ($this->filter->shouldBuffer($bytes)) { + // Then collect bytes into the buffer + if (false === $moreBytes = $this->nextSequence(1)) { + break; + } + + foreach ($moreBytes as $b) { + $bytes[] = $b; + } + } + // And filter them + $bytes = $this->filter->filter($bytes); + } + + $enc = $this->encodeByteSequence($bytes, $size); + + $i = strpos($enc, '=0D=0A'); + $newLineLength = $lineLen + (false === $i ? $size : $i); + + if ($currentLine && $newLineLength >= $thisLineLength) { + $is->write($prepend.$this->standardize($currentLine)); + $currentLine = ''; + $prepend = "=\r\n"; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + + $currentLine .= $enc; + + if (false === $i) { + $lineLen += $size; + } else { + // 6 is the length of '=0D=0A'. + $lineLen = $size - strrpos($enc, '=0D=0A') - 6; + } + } + if (\strlen($currentLine)) { + $is->write($prepend.$this->standardize($currentLine)); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'quoted-printable'. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php new file mode 100644 index 0000000..f3ece43 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php @@ -0,0 +1,96 @@ + + */ +class Swift_Mime_ContentEncoder_QpContentEncoderProxy implements Swift_Mime_ContentEncoder +{ + /** + * @var Swift_Mime_ContentEncoder_QpContentEncoder + */ + private $safeEncoder; + + /** + * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder + */ + private $nativeEncoder; + + /** + * @var string|null + */ + private $charset; + + /** + * Constructor. + * + * @param string|null $charset + */ + public function __construct(Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder, $charset) + { + $this->safeEncoder = $safeEncoder; + $this->nativeEncoder = $nativeEncoder; + $this->charset = $charset; + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->safeEncoder = clone $this->safeEncoder; + $this->nativeEncoder = clone $this->nativeEncoder; + } + + /** + * {@inheritdoc} + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + $this->safeEncoder->charsetChanged($charset); + } + + /** + * {@inheritdoc} + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $this->getEncoder()->encodeByteStream($os, $is, $firstLineOffset, $maxLineLength); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * {@inheritdoc} + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $this->getEncoder()->encodeString($string, $firstLineOffset, $maxLineLength); + } + + /** + * @return Swift_Mime_ContentEncoder + */ + private function getEncoder() + { + return 'utf-8' === $this->charset ? $this->nativeEncoder : $this->safeEncoder; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php new file mode 100644 index 0000000..870e7f4 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php @@ -0,0 +1,65 @@ + + */ +class Swift_Mime_ContentEncoder_RawContentEncoder implements Swift_Mime_ContentEncoder +{ + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $string; + } + + /** + * Encode stream $in to stream $out. + * + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + while (false !== ($bytes = $os->read(8192))) { + $is->write($bytes); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return 'raw'; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php new file mode 100644 index 0000000..42a5177 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php @@ -0,0 +1,41 @@ +setDisposition('inline'); + $this->setId($this->getId()); + } + + /** + * Get the nesting level of this EmbeddedFile. + * + * Returns {@see LEVEL_RELATED}. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_RELATED; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php new file mode 100644 index 0000000..1a952ec --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php @@ -0,0 +1,22 @@ +getName(), "\r\n"); + mb_internal_encoding($old); + + return $newstring; + } + + return parent::encodeString($string, $firstLineOffset, $maxLineLength); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php new file mode 100644 index 0000000..378c480 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php @@ -0,0 +1,65 @@ +safeMap[$byte] = \chr($byte); + } + } + + /** + * Get the name of this encoding scheme. + * + * Returns the string 'Q'. + * + * @return string + */ + public function getName() + { + return 'Q'; + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * @param string $string string to encode + * @param int $firstLineOffset optional + * @param int $maxLineLength optional, 0 indicates the default of 76 chars + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return str_replace([' ', '=20', "=\r\n"], ['_', '_', "\r\n"], + parent::encodeString($string, $firstLineOffset, $maxLineLength) + ); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php new file mode 100644 index 0000000..22caeb2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php @@ -0,0 +1,476 @@ +clearCachedValueIf($charset != $this->charset); + $this->charset = $charset; + if (isset($this->encoder)) { + $this->encoder->charsetChanged($charset); + } + } + + /** + * Get the character set used in this Header. + * + * @return string + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Set the language used in this Header. + * + * For example, for US English, 'en-us'. + * This can be unspecified. + * + * @param string $lang + */ + public function setLanguage($lang) + { + $this->clearCachedValueIf($this->lang != $lang); + $this->lang = $lang; + } + + /** + * Get the language used in this Header. + * + * @return string + */ + public function getLanguage() + { + return $this->lang; + } + + /** + * Set the encoder used for encoding the header. + */ + public function setEncoder(Swift_Mime_HeaderEncoder $encoder) + { + $this->encoder = $encoder; + $this->setCachedValue(null); + } + + /** + * Get the encoder used for encoding this Header. + * + * @return Swift_Mime_HeaderEncoder + */ + public function getEncoder() + { + return $this->encoder; + } + + /** + * Get the name of this header (e.g. charset). + * + * @return string + */ + public function getFieldName() + { + return $this->name; + } + + /** + * Set the maximum length of lines in the header (excluding EOL). + * + * @param int $lineLength + */ + public function setMaxLineLength($lineLength) + { + $this->clearCachedValueIf($this->lineLength != $lineLength); + $this->lineLength = $lineLength; + } + + /** + * Get the maximum permitted length of lines in this Header. + * + * @return int + */ + public function getMaxLineLength() + { + return $this->lineLength; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function toString() + { + return $this->tokensToString($this->toTokens()); + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Set the name of this Header field. + * + * @param string $name + */ + protected function setFieldName($name) + { + $this->name = $name; + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * + * @param string $string as displayed + * @param string $charset of the text + * @param bool $shorten the first line to make remove for header name + * + * @return string + */ + protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false) + { + // Treat token as exactly what was given + $phraseStr = $string; + // If it's not valid + + if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) { + $phraseStr = $this->escapeSpecials($phraseStr, ['"']); + $phraseStr = '"'.$phraseStr.'"'; + } else { + // ... otherwise it needs encoding + // Determine space remaining on line if first line + if ($shorten) { + $usedLength = \strlen($header->getFieldName().': '); + } else { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } + + return $phraseStr; + } + + /** + * Escape special characters in a string (convert to quoted-pairs). + * + * @param string $token + * @param string[] $include additional chars to escape + * + * @return string + */ + private function escapeSpecials($token, $include = []) + { + foreach (array_merge(['\\'], $include) as $char) { + $token = str_replace($char, '\\'.$char, $token); + } + + return $token; + } + + /** + * Encode needed word tokens within a string of input. + * + * @param string $input + * @param string $usedLength optional + * + * @return string + */ + protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1) + { + $value = ''; + + $tokens = $this->getEncodableWordTokens($input); + + foreach ($tokens as $token) { + // See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) { + // Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch ($firstChar) { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) { + $usedLength = \strlen($header->getFieldName().': ') + \strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + + $header->setMaxLineLength(76); // Forcefully override + } else { + $value .= $token; + } + } + + return $value; + } + + /** + * Test if a token needs to be encoded or not. + * + * @param string $token + * + * @return bool + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * + * @param string $string + * + * @return string[] + */ + protected function getEncodableWordTokens($string) + { + $tokens = []; + + $encodedToken = ''; + // Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) { + if ($this->tokenNeedsEncoding($token)) { + $encodedToken .= $token; + } else { + if (\strlen($encodedToken) > 0) { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if (\strlen($encodedToken)) { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + * + * @param string $token token to encode + * @param int $firstLineOffset optional + * + * @return string + */ + protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) + { + // Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->charset; + if (isset($this->lang)) { + $charsetDecl .= '*'.$this->lang; + } + $encodingWrapperLength = \strlen( + '=?'.$charsetDecl.'?'.$this->encoder->getName().'??=' + ); + + if ($firstLineOffset >= 75) { + //Does this logic need to be here? + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + $this->encoder->encodeString( + $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->charset + ) + ); + + if ('iso-2022-jp' !== strtolower($this->charset)) { + // special encoding for iso-2022-jp using mb_encode_mimeheader + foreach ($encodedTextLines as $lineNum => $line) { + $encodedTextLines[$lineNum] = '=?'.$charsetDecl. + '?'.$this->encoder->getName(). + '?'.$line.'?='; + } + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * + * @param string $token + * + * @return string[] + */ + protected function generateTokenLines($token) + { + return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Set a value into the cache. + * + * @param string $value + */ + protected function setCachedValue($value) + { + $this->cachedValue = $value; + } + + /** + * Get the value in the cache. + * + * @return string + */ + protected function getCachedValue() + { + return $this->cachedValue; + } + + /** + * Clear the cached value if $condition is met. + * + * @param bool $condition + */ + protected function clearCachedValueIf($condition) + { + if ($condition) { + $this->setCachedValue(null); + } + } + + /** + * Generate a list of all tokens in the final header. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + if (null === $string) { + $string = $this->getFieldBody(); + } + + $tokens = []; + + // Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) { + $newTokens = $this->generateTokenLines($token); + foreach ($newTokens as $newToken) { + $tokens[] = $newToken; + } + } + + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * + * @param string[] $tokens + * + * @return string + */ + private function tokensToString(array $tokens) + { + $lineCount = 0; + $headerLines = []; + $headerLines[] = $this->name.': '; + $currentLine = &$headerLines[$lineCount++]; + + // Build all tokens back into compliant header + foreach ($tokens as $i => $token) { + // Line longer than specified maximum or token was just a new line + if (("\r\n" == $token) || + ($i > 0 && \strlen($currentLine.$token) > $this->lineLength) + && 0 < \strlen($currentLine)) { + $headerLines[] = ''; + $currentLine = &$headerLines[$lineCount++]; + } + + // Append token to the line + if ("\r\n" != $token) { + $currentLine .= $token; + } + } + + // Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines)."\r\n"; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php new file mode 100644 index 0000000..efe1dad --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php @@ -0,0 +1,113 @@ +setFieldName($name); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_DATE; + } + + /** + * Set the model for the field body. + * + * @param DateTimeInterface $model + */ + public function setFieldBodyModel($model) + { + $this->setDateTime($model); + } + + /** + * Get the model for the field body. + * + * @return DateTimeImmutable + */ + public function getFieldBodyModel() + { + return $this->getDateTime(); + } + + /** + * Get the date-time representing the Date in this Header. + * + * @return DateTimeImmutable + */ + public function getDateTime() + { + return $this->dateTime; + } + + /** + * Set the date-time of the Date in this Header. + * + * If a DateTime instance is provided, it is converted to DateTimeImmutable. + */ + public function setDateTime(DateTimeInterface $dateTime) + { + $this->clearCachedValueIf($this->getCachedValue() != $dateTime->format(DateTime::RFC2822)); + if ($dateTime instanceof DateTime) { + $immutable = new DateTimeImmutable('@'.$dateTime->getTimestamp()); + $dateTime = $immutable->setTimezone($dateTime->getTimezone()); + } + $this->dateTime = $dateTime; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->dateTime)) { + $this->setCachedValue($this->dateTime->format(DateTime::RFC2822)); + } + } + + return $this->getCachedValue(); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php new file mode 100644 index 0000000..dcea0c9 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php @@ -0,0 +1,186 @@ +setFieldName($name); + $this->emailValidator = $emailValidator; + $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder(); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_ID; + } + + /** + * Set the model for the field body. + * + * This method takes a string ID, or an array of IDs. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setId($model); + } + + /** + * Get the model for the field body. + * + * This method returns an array of IDs + * + * @return array + */ + public function getFieldBodyModel() + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * + * @param string|array $id + * + * @throws Swift_RfcComplianceException + */ + public function setId($id) + { + $this->setIds(\is_array($id) ? $id : [$id]); + } + + /** + * Get the ID used in the value of this Header. + * + * If multiple IDs are set only the first is returned. + * + * @return string + */ + public function getId() + { + if (\count($this->ids) > 0) { + return $this->ids[0]; + } + } + + /** + * Set a collection of IDs to use in the value of this Header. + * + * @param string[] $ids + * + * @throws Swift_RfcComplianceException + */ + public function setIds(array $ids) + { + $actualIds = []; + + foreach ($ids as $id) { + $this->assertValidId($id); + $actualIds[] = $id; + } + + $this->clearCachedValueIf($this->ids != $actualIds); + $this->ids = $actualIds; + } + + /** + * Get the list of IDs used in this Header. + * + * @return string[] + */ + public function getIds() + { + return $this->ids; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@see toString()} for that). + * + * @see toString() + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $angleAddrs = []; + + foreach ($this->ids as $id) { + $angleAddrs[] = '<'.$this->addressEncoder->encodeString($id).'>'; + } + + $this->setCachedValue(implode(' ', $angleAddrs)); + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the id passed does not comply with RFC 2822. + * + * @param string $id + * + * @throws Swift_RfcComplianceException + */ + private function assertValidId($id) + { + if (!$this->emailValidator->isValid($id, new RFCValidation())) { + throw new Swift_RfcComplianceException('Invalid ID given <'.$id.'>'); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php new file mode 100644 index 0000000..ddd5e8c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php @@ -0,0 +1,358 @@ +setFieldName($name); + $this->setEncoder($encoder); + $this->emailValidator = $emailValidator; + $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder(); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_MAILBOX; + } + + /** + * Set the model for the field body. + * + * This method takes a string, or an array of addresses. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setNameAddresses($model); + } + + /** + * Get the model for the field body. + * + * This method returns an associative array like {@link getNameAddresses()} + * + * @throws Swift_RfcComplianceException + * + * @return array + */ + public function getFieldBodyModel() + { + return $this->getNameAddresses(); + } + + /** + * Set a list of mailboxes to be shown in this Header. + * + * The mailboxes can be a simple array of addresses, or an array of + * key=>value pairs where (email => personalName). + * Example: + * + * setNameAddresses(array( + * 'chris@swiftmailer.org' => 'Chris Corbyn', + * 'mark@swiftmailer.org' //No associated personal name + * )); + * ?> + * + * + * @see __construct() + * @see setAddresses() + * @see setValue() + * + * @param string|string[] $mailboxes + * + * @throws Swift_RfcComplianceException + */ + public function setNameAddresses($mailboxes) + { + $this->mailboxes = $this->normalizeMailboxes((array) $mailboxes); + $this->setCachedValue(null); //Clear any cached value + } + + /** + * Get the full mailbox list of this Header as an array of valid RFC 2822 strings. + * + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddressStrings()); + * // array ( + * // 0 => Chris Corbyn , + * // 1 => Mark Corbyn + * // ) + * ?> + * + * + * @see getNameAddresses() + * @see toString() + * + * @throws Swift_RfcComplianceException + * + * @return string[] + */ + public function getNameAddressStrings() + { + return $this->createNameAddressStrings($this->getNameAddresses()); + } + + /** + * Get all mailboxes in this Header as key=>value pairs. + * + * The key is the address and the value is the name (or null if none set). + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddresses()); + * // array ( + * // chris@swiftmailer.org => Chris Corbyn, + * // mark@swiftmailer.org => Mark Corbyn + * // ) + * ?> + * + * + * @see getAddresses() + * @see getNameAddressStrings() + * + * @return string[] + */ + public function getNameAddresses() + { + return $this->mailboxes; + } + + /** + * Makes this Header represent a list of plain email addresses with no names. + * + * Example: + * + * setAddresses( + * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld') + * ); + * ?> + * + * + * @see setNameAddresses() + * @see setValue() + * + * @param string[] $addresses + * + * @throws Swift_RfcComplianceException + */ + public function setAddresses($addresses) + { + $this->setNameAddresses(array_values((array) $addresses)); + } + + /** + * Get all email addresses in this Header. + * + * @see getNameAddresses() + * + * @return string[] + */ + public function getAddresses() + { + return array_keys($this->mailboxes); + } + + /** + * Remove one or more addresses from this Header. + * + * @param string|string[] $addresses + */ + public function removeAddresses($addresses) + { + $this->setCachedValue(null); + foreach ((array) $addresses as $address) { + unset($this->mailboxes[$address]); + } + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + public function getFieldBody() + { + // Compute the string value of the header only if needed + if (null === $this->getCachedValue()) { + $this->setCachedValue($this->createMailboxListString($this->mailboxes)); + } + + return $this->getCachedValue(); + } + + /** + * Normalizes a user-input list of mailboxes into consistent key=>value pairs. + * + * @param string[] $mailboxes + * + * @return string[] + */ + protected function normalizeMailboxes(array $mailboxes) + { + $actualMailboxes = []; + + foreach ($mailboxes as $key => $value) { + if (\is_string($key)) { + //key is email addr + $address = $key; + $name = $value; + } else { + $address = $value; + $name = null; + } + $this->assertValidAddress($address); + $actualMailboxes[$address] = $name; + } + + return $actualMailboxes; + } + + /** + * Produces a compliant, formatted display-name based on the string given. + * + * @param string $displayName as displayed + * @param bool $shorten the first line to make remove for header name + * + * @return string + */ + protected function createDisplayNameString($displayName, $shorten = false) + { + return $this->createPhrase($this, $displayName, $this->getCharset(), $this->getEncoder(), $shorten); + } + + /** + * Creates a string form of all the mailboxes in the passed array. + * + * @param string[] $mailboxes + * + * @throws Swift_RfcComplianceException + * + * @return string + */ + protected function createMailboxListString(array $mailboxes) + { + return implode(', ', $this->createNameAddressStrings($mailboxes)); + } + + /** + * Redefine the encoding requirements for mailboxes. + * + * All "specials" must be encoded as the full header value will not be quoted + * + * @see RFC 2822 3.2.1 + * + * @param string $token + * + * @return bool + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token); + } + + /** + * Return an array of strings conforming the the name-addr spec of RFC 2822. + * + * @param string[] $mailboxes + * + * @return string[] + */ + private function createNameAddressStrings(array $mailboxes) + { + $strings = []; + + foreach ($mailboxes as $email => $name) { + $mailboxStr = $this->addressEncoder->encodeString($email); + if (null !== $name) { + $nameStr = $this->createDisplayNameString($name, empty($strings)); + $mailboxStr = $nameStr.' <'.$mailboxStr.'>'; + } + $strings[] = $mailboxStr; + } + + return $strings; + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException if invalid + */ + private function assertValidAddress($address) + { + if (!$this->emailValidator->isValid($address, new RFCValidation())) { + throw new Swift_RfcComplianceException('Address in mailbox given ['.$address.'] does not comply with RFC 2822, 3.6.2.'); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php new file mode 100644 index 0000000..fafb5ba --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php @@ -0,0 +1,135 @@ + + * + * @deprecated since SwiftMailer 6.1.0; use Swift_Signers_DKIMSigner instead. + */ +class Swift_Mime_Headers_OpenDKIMHeader implements Swift_Mime_Header +{ + /** + * The value of this Header. + * + * @var string + */ + private $value; + + /** + * The name of this Header. + * + * @var string + */ + private $fieldName; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->fieldName = $name; + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * + * This method takes a string for the field value. + * + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * + * This method returns a string. + * + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the (unencoded) value of this header. + * + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() + { + return $this->value; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * + * @return string + */ + public function toString() + { + return $this->fieldName.': '.$this->value."\r\n"; + } + + /** + * Set the Header FieldName. + * + * @see Swift_Mime_Header::getFieldName() + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Ignored. + */ + public function setCharset($charset) + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php new file mode 100644 index 0000000..47c15e6 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php @@ -0,0 +1,255 @@ +paramEncoder = $paramEncoder; + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PARAMETERIZED; + } + + /** + * Set the character set used in this Header. + * + * @param string $charset + */ + public function setCharset($charset) + { + parent::setCharset($charset); + if (isset($this->paramEncoder)) { + $this->paramEncoder->charsetChanged($charset); + } + } + + /** + * Set the value of $parameter. + * + * @param string $parameter + * @param string $value + */ + public function setParameter($parameter, $value) + { + $this->setParameters(array_merge($this->getParameters(), [$parameter => $value])); + } + + /** + * Get the value of $parameter. + * + * @param string $parameter + * + * @return string + */ + public function getParameter($parameter) + { + $params = $this->getParameters(); + + return $params[$parameter] ?? null; + } + + /** + * Set an associative array of parameter names mapped to values. + * + * @param string[] $parameters + */ + public function setParameters(array $parameters) + { + $this->clearCachedValueIf($this->params != $parameters); + $this->params = $parameters; + } + + /** + * Returns an associative array of parameter names mapped to values. + * + * @return string[] + */ + public function getParameters() + { + return $this->params; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() //TODO: Check caching here + { + $body = parent::getFieldBody(); + foreach ($this->params as $name => $value) { + if (null !== $value) { + // Add the parameter + $body .= '; '.$this->createParameter($name, $value); + } + } + + return $body; + } + + /** + * Generate a list of all tokens in the final header. + * + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + $tokens = parent::toTokens(parent::getFieldBody()); + + // Try creating any parameters + foreach ($this->params as $name => $value) { + if (null !== $value) { + // Add the semi-colon separator + $tokens[\count($tokens) - 1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines( + ' '.$this->createParameter($name, $value) + )); + } + } + + return $tokens; + } + + /** + * Render a RFC 2047 compliant header parameter from the $name and $value. + * + * @param string $name + * @param string $value + * + * @return string + */ + private function createParameter($name, $value) + { + $origValue = $value; + + $encoded = false; + // Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1; + $firstLineOffset = 0; + + // If it's not already a valid parameter value... + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + // TODO: text, or something else?? + // ... and it's not ascii + if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) { + $encoded = true; + // Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1; + $firstLineOffset = \strlen( + $this->getCharset()."'".$this->getLanguage()."'" + ); + } + } + + // Encode if we need to + if ($encoded || \strlen($value) > $maxValueLength) { + if (isset($this->paramEncoder)) { + $value = $this->paramEncoder->encodeString( + $origValue, $firstLineOffset, $maxValueLength, $this->getCharset() + ); + } else { + // We have to go against RFC 2183/2231 in some areas for interoperability + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = isset($this->paramEncoder) ? explode("\r\n", $value) : [$value]; + + // Need to add indices + if (\count($valueLines) > 1) { + $paramLines = []; + foreach ($valueLines as $i => $line) { + $paramLines[] = $name.'*'.$i. + $this->getEndOfParameterValue($line, true, 0 == $i); + } + + return implode(";\r\n ", $paramLines); + } else { + return $name.$this->getEndOfParameterValue( + $valueLines[0], $encoded, true + ); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * + * @param string $value to append + * @param bool $encoded + * @param bool $firstLine + * + * @return string + */ + private function getEndOfParameterValue($value, $encoded = false, $firstLine = false) + { + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + $value = '"'.$value.'"'; + } + $prepend = '='; + if ($encoded) { + $prepend = '*='; + if ($firstLine) { + $prepend = '*='.$this->getCharset()."'".$this->getLanguage(). + "'"; + } + } + + return $prepend.$value; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php new file mode 100644 index 0000000..81b421e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php @@ -0,0 +1,153 @@ +setFieldName($name); + $this->emailValidator = $emailValidator; + $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder(); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PATH; + } + + /** + * Set the model for the field body. + * This method takes a string for an address. + * + * @param string $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setAddress($model); + } + + /** + * Get the model for the field body. + * This method returns a string email address. + * + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getAddress(); + } + + /** + * Set the Address which should appear in this Header. + * + * @param string $address + * + * @throws Swift_RfcComplianceException + */ + public function setAddress($address) + { + if (null === $address) { + $this->address = null; + } elseif ('' == $address) { + $this->address = ''; + } else { + $this->assertValidAddress($address); + $this->address = $address; + } + $this->setCachedValue(null); + } + + /** + * Get the address which is used in this Header (if any). + * + * Null is returned if no address is set. + * + * @return string + */ + public function getAddress() + { + return $this->address; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->address)) { + $address = $this->addressEncoder->encodeString($this->address); + $this->setCachedValue('<'.$address.'>'); + } + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException If address is invalid + */ + private function assertValidAddress($address) + { + if (!$this->emailValidator->isValid($address, new RFCValidation())) { + throw new Swift_RfcComplianceException('Address set in PathHeader does not comply with addr-spec of RFC 2822.'); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php new file mode 100644 index 0000000..64f160d --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php @@ -0,0 +1,109 @@ +setFieldName($name); + $this->setEncoder($encoder); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * + * This method takes a string for the field value. + * + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * + * This method returns a string. + * + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the (unencoded) value of this header. + * + * @param string $value + */ + public function setValue($value) + { + $this->clearCachedValueIf($this->value != $value); + $this->value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $this->setCachedValue( + $this->encodeWords($this, $this->value) + ); + } + + return $this->getCachedValue(); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/IdGenerator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/IdGenerator.php new file mode 100644 index 0000000..3ce35f2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/IdGenerator.php @@ -0,0 +1,54 @@ +idRight = $idRight; + } + + /** + * Returns the right-hand side of the "@" used in all generated IDs. + * + * @return string + */ + public function getIdRight() + { + return $this->idRight; + } + + /** + * Sets the right-hand side of the "@" to use in all generated IDs. + * + * @param string $idRight + */ + public function setIdRight($idRight) + { + $this->idRight = $idRight; + } + + /** + * @return string + */ + public function generateId() + { + // 32 hex values for the left part + return bin2hex(random_bytes(16)).'@'.$this->idRight; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php new file mode 100644 index 0000000..fa0726a --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php @@ -0,0 +1,199 @@ +setContentType('text/plain'); + if (null !== $charset) { + $this->setCharset($charset); + } + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * @param string $charset optional + * + * @return $this + */ + public function setBody($body, $contentType = null, $charset = null) + { + if (isset($charset)) { + $this->setCharset($charset); + } + $body = $this->convertString($body); + + parent::setBody($body, $contentType); + + return $this; + } + + /** + * Get the character set of this entity. + * + * @return string + */ + public function getCharset() + { + return $this->getHeaderParameter('Content-Type', 'charset'); + } + + /** + * Set the character set of this entity. + * + * @param string $charset + * + * @return $this + */ + public function setCharset($charset) + { + $this->setHeaderParameter('Content-Type', 'charset', $charset); + if ($charset !== $this->userCharset) { + $this->clearCache(); + } + $this->userCharset = $charset; + parent::charsetChanged($charset); + + return $this; + } + + /** + * Get the format of this entity (i.e. flowed or fixed). + * + * @return string + */ + public function getFormat() + { + return $this->getHeaderParameter('Content-Type', 'format'); + } + + /** + * Set the format of this entity (flowed or fixed). + * + * @param string $format + * + * @return $this + */ + public function setFormat($format) + { + $this->setHeaderParameter('Content-Type', 'format', $format); + $this->userFormat = $format; + + return $this; + } + + /** + * Test if delsp is being used for this entity. + * + * @return bool + */ + public function getDelSp() + { + return 'yes' === $this->getHeaderParameter('Content-Type', 'delsp'); + } + + /** + * Turn delsp on or off for this entity. + * + * @param bool $delsp + * + * @return $this + */ + public function setDelSp($delsp = true) + { + $this->setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null); + $this->userDelSp = $delsp; + + return $this; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED + * + * @return int + */ + public function getNestingLevel() + { + return $this->nestingLevel; + } + + /** + * Receive notification that the charset has changed on this document, or a + * parent document. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** Fix the content-type and encoding of this entity */ + protected function fixHeaders() + { + parent::fixHeaders(); + if (\count($this->getChildren())) { + $this->setHeaderParameter('Content-Type', 'charset', null); + $this->setHeaderParameter('Content-Type', 'format', null); + $this->setHeaderParameter('Content-Type', 'delsp', null); + } else { + $this->setCharset($this->userCharset); + $this->setFormat($this->userFormat); + $this->setDelSp($this->userDelSp); + } + } + + /** Set the nesting level of this entity */ + protected function setNestingLevel($level) + { + $this->nestingLevel = $level; + } + + /** Encode charset when charset is not utf-8 */ + protected function convertString($string) + { + $charset = strtolower($this->getCharset()); + if (!\in_array($charset, ['utf-8', 'iso-8859-1', 'iso-8859-15', ''])) { + return mb_convert_encoding($string, $charset, 'utf-8'); + } + + return $string; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php new file mode 100644 index 0000000..b4345f4 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php @@ -0,0 +1,194 @@ +encoder = $encoder; + $this->paramEncoder = $paramEncoder; + $this->emailValidator = $emailValidator; + $this->charset = $charset; + $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder(); + } + + /** + * Create a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string|null $addresses + * + * @return Swift_Mime_Header + */ + public function createMailboxHeader($name, $addresses = null) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $this->encoder, $this->emailValidator, $this->addressEncoder); + if (isset($addresses)) { + $header->setFieldBodyModel($addresses); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Date header using $dateTime. + * + * @param string $name + * + * @return Swift_Mime_Header + */ + public function createDateHeader($name, DateTimeInterface $dateTime = null) + { + $header = new Swift_Mime_Headers_DateHeader($name); + if (isset($dateTime)) { + $header->setFieldBodyModel($dateTime); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Create a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + * + * @return Swift_Mime_Header + */ + public function createTextHeader($name, $value = null) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->encoder); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + * + * @return Swift_Mime_Headers_ParameterizedHeader + */ + public function createParameterizedHeader($name, $value = null, $params = []) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, $this->encoder, ('content-disposition' == strtolower($name)) ? $this->paramEncoder : null); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + foreach ($params as $k => $v) { + $header->setParameter($k, $v); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + * + * @return Swift_Mime_Header + */ + public function createIdHeader($name, $ids = null) + { + $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->emailValidator); + if (isset($ids)) { + $header->setFieldBodyModel($ids); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + * + * @return Swift_Mime_Header + */ + public function createPathHeader($name, $path = null) + { + $header = new Swift_Mime_Headers_PathHeader($name, $this->emailValidator); + if (isset($path)) { + $header->setFieldBodyModel($path); + } + $this->setHeaderCharset($header); + + return $header; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + $this->encoder->charsetChanged($charset); + $this->paramEncoder->charsetChanged($charset); + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->encoder = clone $this->encoder; + $this->paramEncoder = clone $this->paramEncoder; + } + + /** Apply the charset to the Header */ + private function setHeaderCharset(Swift_Mime_Header $header) + { + if (isset($this->charset)) { + $header->setCharset($this->charset); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php new file mode 100644 index 0000000..d55cf66 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php @@ -0,0 +1,399 @@ +factory = $factory; + if (isset($charset)) { + $this->setCharset($charset); + } + } + + public function newInstance() + { + return new self($this->factory); + } + + /** + * Set the charset used by these headers. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->charset = $charset; + $this->factory->charsetChanged($charset); + $this->notifyHeadersOfCharset($charset); + } + + /** + * Add a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string $addresses + */ + public function addMailboxHeader($name, $addresses = null) + { + $this->storeHeader($name, $this->factory->createMailboxHeader($name, $addresses)); + } + + /** + * Add a new Date header using $dateTime. + * + * @param string $name + */ + public function addDateHeader($name, DateTimeInterface $dateTime = null) + { + $this->storeHeader($name, $this->factory->createDateHeader($name, $dateTime)); + } + + /** + * Add a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + */ + public function addTextHeader($name, $value = null) + { + $this->storeHeader($name, $this->factory->createTextHeader($name, $value)); + } + + /** + * Add a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + */ + public function addParameterizedHeader($name, $value = null, $params = []) + { + $this->storeHeader($name, $this->factory->createParameterizedHeader($name, $value, $params)); + } + + /** + * Add a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + */ + public function addIdHeader($name, $ids = null) + { + $this->storeHeader($name, $this->factory->createIdHeader($name, $ids)); + } + + /** + * Add a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + */ + public function addPathHeader($name, $path = null) + { + $this->storeHeader($name, $this->factory->createPathHeader($name, $path)); + } + + /** + * Returns true if at least one header with the given $name exists. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + * + * @return bool + */ + public function has($name, $index = 0) + { + $lowerName = strtolower($name); + + if (!\array_key_exists($lowerName, $this->headers)) { + return false; + } + + if (\func_num_args() < 2) { + // index was not specified, so we only need to check that there is at least one header value set + return (bool) \count($this->headers[$lowerName]); + } + + return \array_key_exists($index, $this->headers[$lowerName]); + } + + /** + * Set a header in the HeaderSet. + * + * The header may be a previously fetched header via {@link get()} or it may + * be one that has been created separately. + * + * If $index is specified, the header will be inserted into the set at this + * offset. + * + * @param int $index + */ + public function set(Swift_Mime_Header $header, $index = 0) + { + $this->storeHeader($header->getFieldName(), $header, $index); + } + + /** + * Get the header with the given $name. + * + * If multiple headers match, the actual one may be specified by $index. + * Returns NULL if none present. + * + * @param string $name + * @param int $index + * + * @return Swift_Mime_Header + */ + public function get($name, $index = 0) + { + $name = strtolower($name); + + if (\func_num_args() < 2) { + if ($this->has($name)) { + $values = array_values($this->headers[$name]); + + return array_shift($values); + } + } else { + if ($this->has($name, $index)) { + return $this->headers[$name][$index]; + } + } + } + + /** + * Get all headers with the given $name. + * + * @param string $name + * + * @return array + */ + public function getAll($name = null) + { + if (!isset($name)) { + $headers = []; + foreach ($this->headers as $collection) { + $headers = array_merge($headers, $collection); + } + + return $headers; + } + + $lowerName = strtolower($name); + if (!\array_key_exists($lowerName, $this->headers)) { + return []; + } + + return $this->headers[$lowerName]; + } + + /** + * Return the name of all Headers. + * + * @return array + */ + public function listAll() + { + $headers = $this->headers; + if ($this->canSort()) { + uksort($headers, [$this, 'sortHeaders']); + } + + return array_keys($headers); + } + + /** + * Remove the header with the given $name if it's set. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + */ + public function remove($name, $index = 0) + { + $lowerName = strtolower($name); + unset($this->headers[$lowerName][$index]); + } + + /** + * Remove all headers with the given $name. + * + * @param string $name + */ + public function removeAll($name) + { + $lowerName = strtolower($name); + unset($this->headers[$lowerName]); + } + + /** + * Define a list of Header names as an array in the correct order. + * + * These Headers will be output in the given order where present. + */ + public function defineOrdering(array $sequence) + { + $this->order = array_flip(array_map('strtolower', $sequence)); + } + + /** + * Set a list of header names which must always be displayed when set. + * + * Usually headers without a field value won't be output unless set here. + */ + public function setAlwaysDisplayed(array $names) + { + $this->required = array_flip(array_map('strtolower', $names)); + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** + * Returns a string with a representation of all headers. + * + * @return string + */ + public function toString() + { + $string = ''; + $headers = $this->headers; + if ($this->canSort()) { + uksort($headers, [$this, 'sortHeaders']); + } + foreach ($headers as $collection) { + foreach ($collection as $header) { + if ($this->isDisplayed($header) || '' != $header->getFieldBody()) { + $string .= $header->toString(); + } + } + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** Save a Header to the internal collection */ + private function storeHeader($name, Swift_Mime_Header $header, $offset = null) + { + if (!isset($this->headers[strtolower($name)])) { + $this->headers[strtolower($name)] = []; + } + if (!isset($offset)) { + $this->headers[strtolower($name)][] = $header; + } else { + $this->headers[strtolower($name)][$offset] = $header; + } + } + + /** Test if the headers can be sorted */ + private function canSort() + { + return \count($this->order) > 0; + } + + /** uksort() algorithm for Header ordering */ + private function sortHeaders($a, $b) + { + $lowerA = strtolower($a); + $lowerB = strtolower($b); + $aPos = \array_key_exists($lowerA, $this->order) ? $this->order[$lowerA] : -1; + $bPos = \array_key_exists($lowerB, $this->order) ? $this->order[$lowerB] : -1; + + if (-1 === $aPos && -1 === $bPos) { + // just be sure to be determinist here + return $a > $b ? -1 : 1; + } + + if (-1 == $aPos) { + return 1; + } elseif (-1 == $bPos) { + return -1; + } + + return $aPos < $bPos ? -1 : 1; + } + + /** Test if the given Header is always displayed */ + private function isDisplayed(Swift_Mime_Header $header) + { + return \array_key_exists(strtolower($header->getFieldName()), $this->required); + } + + /** Notify all Headers of the new charset */ + private function notifyHeadersOfCharset($charset) + { + foreach ($this->headers as $headerGroup) { + foreach ($headerGroup as $header) { + $header->setCharset($charset); + } + } + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->factory = clone $this->factory; + foreach ($this->headers as $groupKey => $headerGroup) { + foreach ($headerGroup as $key => $header) { + $this->headers[$groupKey][$key] = clone $header; + } + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php new file mode 100644 index 0000000..62da165 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php @@ -0,0 +1,642 @@ +getHeaders()->defineOrdering([ + 'Return-Path', + 'Received', + 'DKIM-Signature', + 'DomainKey-Signature', + 'Sender', + 'Message-ID', + 'Date', + 'Subject', + 'From', + 'Reply-To', + 'To', + 'Cc', + 'Bcc', + 'MIME-Version', + 'Content-Type', + 'Content-Transfer-Encoding', + ]); + $this->getHeaders()->setAlwaysDisplayed(['Date', 'Message-ID', 'From']); + $this->getHeaders()->addTextHeader('MIME-Version', '1.0'); + $this->setDate(new DateTimeImmutable()); + $this->setId($this->getId()); + $this->getHeaders()->addMailboxHeader('From'); + } + + /** + * Always returns {@link LEVEL_TOP} for a message instance. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_TOP; + } + + /** + * Set the subject of this message. + * + * @param string $subject + * + * @return $this + */ + public function setSubject($subject) + { + if (!$this->setHeaderFieldModel('Subject', $subject)) { + $this->getHeaders()->addTextHeader('Subject', $subject); + } + + return $this; + } + + /** + * Get the subject of this message. + * + * @return string + */ + public function getSubject() + { + return $this->getHeaderFieldModel('Subject'); + } + + /** + * Set the date at which this message was created. + * + * @return $this + */ + public function setDate(DateTimeInterface $dateTime) + { + if (!$this->setHeaderFieldModel('Date', $dateTime)) { + $this->getHeaders()->addDateHeader('Date', $dateTime); + } + + return $this; + } + + /** + * Get the date at which this message was created. + * + * @return DateTimeInterface + */ + public function getDate() + { + return $this->getHeaderFieldModel('Date'); + } + + /** + * Set the return-path (the bounce address) of this message. + * + * @param string $address + * + * @return $this + */ + public function setReturnPath($address) + { + if (!$this->setHeaderFieldModel('Return-Path', $address)) { + $this->getHeaders()->addPathHeader('Return-Path', $address); + } + + return $this; + } + + /** + * Get the return-path (bounce address) of this message. + * + * @return string + */ + public function getReturnPath() + { + return $this->getHeaderFieldModel('Return-Path'); + } + + /** + * Set the sender of this message. + * + * This does not override the From field, but it has a higher significance. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function setSender($address, $name = null) + { + if (!\is_array($address) && isset($name)) { + $address = [$address => $name]; + } + + if (!$this->setHeaderFieldModel('Sender', (array) $address)) { + $this->getHeaders()->addMailboxHeader('Sender', (array) $address); + } + + return $this; + } + + /** + * Get the sender of this message. + * + * @return string + */ + public function getSender() + { + return $this->getHeaderFieldModel('Sender'); + } + + /** + * Add a From: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function addFrom($address, $name = null) + { + $current = $this->getFrom(); + $current[$address] = $name; + + return $this->setFrom($current); + } + + /** + * Set the from address of this message. + * + * You may pass an array of addresses if this message is from multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string|array $addresses + * @param string $name optional + * + * @return $this + */ + public function setFrom($addresses, $name = null) + { + if (!\is_array($addresses) && isset($name)) { + $addresses = [$addresses => $name]; + } + + if (!$this->setHeaderFieldModel('From', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('From', (array) $addresses); + } + + return $this; + } + + /** + * Get the from address of this message. + * + * @return mixed + */ + public function getFrom() + { + return $this->getHeaderFieldModel('From'); + } + + /** + * Add a Reply-To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function addReplyTo($address, $name = null) + { + $current = $this->getReplyTo(); + $current[$address] = $name; + + return $this->setReplyTo($current); + } + + /** + * Set the reply-to address of this message. + * + * You may pass an array of addresses if replies will go to multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return $this + */ + public function setReplyTo($addresses, $name = null) + { + if (!\is_array($addresses) && isset($name)) { + $addresses = [$addresses => $name]; + } + + if (!$this->setHeaderFieldModel('Reply-To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses); + } + + return $this; + } + + /** + * Get the reply-to address of this message. + * + * @return string + */ + public function getReplyTo() + { + return $this->getHeaderFieldModel('Reply-To'); + } + + /** + * Add a To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function addTo($address, $name = null) + { + $current = $this->getTo(); + $current[$address] = $name; + + return $this->setTo($current); + } + + /** + * Set the to addresses of this message. + * + * If multiple recipients will receive the message an array should be used. + * Example: array('receiver@domain.org', 'other@domain.org' => 'A name') + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return $this + */ + public function setTo($addresses, $name = null) + { + if (!\is_array($addresses) && isset($name)) { + $addresses = [$addresses => $name]; + } + + if (!$this->setHeaderFieldModel('To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('To', (array) $addresses); + } + + return $this; + } + + /** + * Get the To addresses of this message. + * + * @return array + */ + public function getTo() + { + return $this->getHeaderFieldModel('To'); + } + + /** + * Add a Cc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function addCc($address, $name = null) + { + $current = $this->getCc(); + $current[$address] = $name; + + return $this->setCc($current); + } + + /** + * Set the Cc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return $this + */ + public function setCc($addresses, $name = null) + { + if (!\is_array($addresses) && isset($name)) { + $addresses = [$addresses => $name]; + } + + if (!$this->setHeaderFieldModel('Cc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Cc address of this message. + * + * @return array + */ + public function getCc() + { + return $this->getHeaderFieldModel('Cc'); + } + + /** + * Add a Bcc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return $this + */ + public function addBcc($address, $name = null) + { + $current = $this->getBcc(); + $current[$address] = $name; + + return $this->setBcc($current); + } + + /** + * Set the Bcc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return $this + */ + public function setBcc($addresses, $name = null) + { + if (!\is_array($addresses) && isset($name)) { + $addresses = [$addresses => $name]; + } + + if (!$this->setHeaderFieldModel('Bcc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Bcc addresses of this message. + * + * @return array + */ + public function getBcc() + { + return $this->getHeaderFieldModel('Bcc'); + } + + /** + * Set the priority of this message. + * + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * + * @param int $priority + * + * @return $this + */ + public function setPriority($priority) + { + $priorityMap = [ + self::PRIORITY_HIGHEST => 'Highest', + self::PRIORITY_HIGH => 'High', + self::PRIORITY_NORMAL => 'Normal', + self::PRIORITY_LOW => 'Low', + self::PRIORITY_LOWEST => 'Lowest', + ]; + $pMapKeys = array_keys($priorityMap); + if ($priority > max($pMapKeys)) { + $priority = max($pMapKeys); + } elseif ($priority < min($pMapKeys)) { + $priority = min($pMapKeys); + } + if (!$this->setHeaderFieldModel('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority]))) { + $this->getHeaders()->addTextHeader('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority])); + } + + return $this; + } + + /** + * Get the priority of this message. + * + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + * + * @return int + */ + public function getPriority() + { + list($priority) = sscanf($this->getHeaderFieldModel('X-Priority'), + '%[1-5]' + ); + + return $priority ?? 3; + } + + /** + * Ask for a delivery receipt from the recipient to be sent to $addresses. + * + * @param array $addresses + * + * @return $this + */ + public function setReadReceiptTo($addresses) + { + if (!$this->setHeaderFieldModel('Disposition-Notification-To', $addresses)) { + $this->getHeaders() + ->addMailboxHeader('Disposition-Notification-To', $addresses); + } + + return $this; + } + + /** + * Get the addresses to which a read-receipt will be sent. + * + * @return string + */ + public function getReadReceiptTo() + { + return $this->getHeaderFieldModel('Disposition-Notification-To'); + } + + /** + * Attach a {@link Swift_Mime_SimpleMimeEntity} such as an Attachment or MimePart. + * + * @return $this + */ + public function attach(Swift_Mime_SimpleMimeEntity $entity) + { + $this->setChildren(array_merge($this->getChildren(), [$entity])); + + return $this; + } + + /** + * Remove an already attached entity. + * + * @return $this + */ + public function detach(Swift_Mime_SimpleMimeEntity $entity) + { + $newChildren = []; + foreach ($this->getChildren() as $child) { + if ($entity !== $child) { + $newChildren[] = $child; + } + } + $this->setChildren($newChildren); + + return $this; + } + + /** + * Attach a {@link Swift_Mime_SimpleMimeEntity} and return it's CID source. + * + * This method should be used when embedding images or other data in a message. + * + * @return string + */ + public function embed(Swift_Mime_SimpleMimeEntity $entity) + { + $this->attach($entity); + + return 'cid:'.$entity->getId(); + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + if (\count($children = $this->getChildren()) > 0 && '' != $this->getBody()) { + $this->setChildren(array_merge([$this->becomeMimePart()], $children)); + $string = parent::toString(); + $this->setChildren($children); + } else { + $string = parent::toString(); + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (\count($children = $this->getChildren()) > 0 && '' != $this->getBody()) { + $this->setChildren(array_merge([$this->becomeMimePart()], $children)); + parent::toByteStream($is); + $this->setChildren($children); + } else { + parent::toByteStream($is); + } + } + + /** @see Swift_Mime_SimpleMimeEntity::getIdField() */ + protected function getIdField() + { + return 'Message-ID'; + } + + /** Turn the body of this message into a child of itself if needed */ + protected function becomeMimePart() + { + $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(), + $this->getCache(), $this->getIdGenerator(), $this->userCharset + ); + $part->setContentType($this->userContentType); + $part->setBody($this->getBody()); + $part->setFormat($this->userFormat); + $part->setDelSp($this->userDelSp); + $part->setNestingLevel($this->getTopNestingLevel()); + + return $part; + } + + /** Get the highest nesting level nested inside this message */ + private function getTopNestingLevel() + { + $highestLevel = $this->getNestingLevel(); + foreach ($this->getChildren() as $child) { + $childLevel = $child->getNestingLevel(); + if ($highestLevel < $childLevel) { + $highestLevel = $childLevel; + } + } + + return $highestLevel; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php new file mode 100644 index 0000000..fa18aa8 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php @@ -0,0 +1,826 @@ + [self::LEVEL_TOP, self::LEVEL_MIXED], + 'multipart/alternative' => [self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE], + 'multipart/related' => [self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED], + ]; + + /** A set of filter rules to define what level an entity should be nested at */ + private $compoundLevelFilters = []; + + /** The nesting level of this entity */ + private $nestingLevel = self::LEVEL_ALTERNATIVE; + + /** A KeyCache instance used during encoding and streaming */ + private $cache; + + /** Direct descendants of this entity */ + private $immediateChildren = []; + + /** All descendants of this entity */ + private $children = []; + + /** The maximum line length of the body of this entity */ + private $maxLineLength = 78; + + /** The order in which alternative mime types should appear */ + private $alternativePartOrder = [ + 'text/plain' => 1, + 'text/html' => 2, + 'multipart/related' => 3, + ]; + + /** The CID of this entity */ + private $id; + + /** The key used for accessing the cache */ + private $cacheKey; + + protected $userContentType; + + /** + * Create a new SimpleMimeEntity with $headers, $encoder and $cache. + */ + public function __construct(Swift_Mime_SimpleHeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_IdGenerator $idGenerator) + { + $this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values + $this->cache = $cache; + $this->headers = $headers; + $this->idGenerator = $idGenerator; + $this->setEncoder($encoder); + $this->headers->defineOrdering(['Content-Type', 'Content-Transfer-Encoding']); + + // This array specifies that, when the entire MIME document contains + // $compoundLevel, then for each child within $level, if its Content-Type + // is $contentType then it should be treated as if it's level is + // $neededLevel instead. I tried to write that unambiguously! :-\ + // Data Structure: + // array ( + // $compoundLevel => array( + // $level => array( + // $contentType => $neededLevel + // ) + // ) + // ) + + $this->compoundLevelFilters = [ + (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => [ + self::LEVEL_ALTERNATIVE => [ + 'text/plain' => self::LEVEL_ALTERNATIVE, + 'text/html' => self::LEVEL_RELATED, + ], + ], + ]; + + $this->id = $this->idGenerator->generateId(); + } + + /** + * Generate a new Content-ID or Message-ID for this MIME entity. + * + * @return string + */ + public function generateId() + { + $this->setId($this->idGenerator->generateId()); + + return $this->id; + } + + /** + * Get the {@link Swift_Mime_SimpleHeaderSet} for this entity. + * + * @return Swift_Mime_SimpleHeaderSet + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE + * + * @return int + */ + public function getNestingLevel() + { + return $this->nestingLevel; + } + + /** + * Get the Content-type of this entity. + * + * @return string + */ + public function getContentType() + { + return $this->getHeaderFieldModel('Content-Type'); + } + + /** + * Get the Body Content-type of this entity. + * + * @return string + */ + public function getBodyContentType() + { + return $this->userContentType; + } + + /** + * Set the Content-type of this entity. + * + * @param string $type + * + * @return $this + */ + public function setContentType($type) + { + $this->setContentTypeInHeaders($type); + // Keep track of the value so that if the content-type changes automatically + // due to added child entities, it can be restored if they are later removed + $this->userContentType = $type; + + return $this; + } + + /** + * Get the CID of this entity. + * + * The CID will only be present in headers if a Content-ID header is present. + * + * @return string + */ + public function getId() + { + $tmp = (array) $this->getHeaderFieldModel($this->getIdField()); + + return $this->headers->has($this->getIdField()) ? current($tmp) : $this->id; + } + + /** + * Set the CID of this entity. + * + * @param string $id + * + * @return $this + */ + public function setId($id) + { + if (!$this->setHeaderFieldModel($this->getIdField(), $id)) { + $this->headers->addIdHeader($this->getIdField(), $id); + } + $this->id = $id; + + return $this; + } + + /** + * Get the description of this entity. + * + * This value comes from the Content-Description header if set. + * + * @return string + */ + public function getDescription() + { + return $this->getHeaderFieldModel('Content-Description'); + } + + /** + * Set the description of this entity. + * + * This method sets a value in the Content-ID header. + * + * @param string $description + * + * @return $this + */ + public function setDescription($description) + { + if (!$this->setHeaderFieldModel('Content-Description', $description)) { + $this->headers->addTextHeader('Content-Description', $description); + } + + return $this; + } + + /** + * Get the maximum line length of the body of this entity. + * + * @return int + */ + public function getMaxLineLength() + { + return $this->maxLineLength; + } + + /** + * Set the maximum line length of lines in this body. + * + * Though not enforced by the library, lines should not exceed 1000 chars. + * + * @param int $length + * + * @return $this + */ + public function setMaxLineLength($length) + { + $this->maxLineLength = $length; + + return $this; + } + + /** + * Get all children added to this entity. + * + * @return Swift_Mime_SimpleMimeEntity[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * Set all children of this entity. + * + * @param Swift_Mime_SimpleMimeEntity[] $children + * @param int $compoundLevel For internal use only + * + * @return $this + */ + public function setChildren(array $children, $compoundLevel = null) + { + // TODO: Try to refactor this logic + $compoundLevel = $compoundLevel ?? $this->getCompoundLevel($children); + $immediateChildren = []; + $grandchildren = []; + $newContentType = $this->userContentType; + + foreach ($children as $child) { + $level = $this->getNeededChildLevel($child, $compoundLevel); + if (empty($immediateChildren)) { + //first iteration + $immediateChildren = [$child]; + } else { + $nextLevel = $this->getNeededChildLevel($immediateChildren[0], $compoundLevel); + if ($nextLevel == $level) { + $immediateChildren[] = $child; + } elseif ($level < $nextLevel) { + // Re-assign immediateChildren to grandchildren + $grandchildren = array_merge($grandchildren, $immediateChildren); + // Set new children + $immediateChildren = [$child]; + } else { + $grandchildren[] = $child; + } + } + } + + if ($immediateChildren) { + $lowestLevel = $this->getNeededChildLevel($immediateChildren[0], $compoundLevel); + + // Determine which composite media type is needed to accommodate the + // immediate children + foreach ($this->compositeRanges as $mediaType => $range) { + if ($lowestLevel > $range[0] && $lowestLevel <= $range[1]) { + $newContentType = $mediaType; + + break; + } + } + + // Put any grandchildren in a subpart + if (!empty($grandchildren)) { + $subentity = $this->createChild(); + $subentity->setNestingLevel($lowestLevel); + $subentity->setChildren($grandchildren, $compoundLevel); + array_unshift($immediateChildren, $subentity); + } + } + + $this->immediateChildren = $immediateChildren; + $this->children = $children; + $this->setContentTypeInHeaders($newContentType); + $this->fixHeaders(); + $this->sortChildren(); + + return $this; + } + + /** + * Get the body of this entity as a string. + * + * @return string + */ + public function getBody() + { + return $this->body instanceof Swift_OutputByteStream ? $this->readStream($this->body) : $this->body; + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * + * @return $this + */ + public function setBody($body, $contentType = null) + { + if ($body !== $this->body) { + $this->clearCache(); + } + + $this->body = $body; + if (null !== $contentType) { + $this->setContentType($contentType); + } + + return $this; + } + + /** + * Get the encoder used for the body of this entity. + * + * @return Swift_Mime_ContentEncoder + */ + public function getEncoder() + { + return $this->encoder; + } + + /** + * Set the encoder used for the body of this entity. + * + * @return $this + */ + public function setEncoder(Swift_Mime_ContentEncoder $encoder) + { + if ($encoder !== $this->encoder) { + $this->clearCache(); + } + + $this->encoder = $encoder; + $this->setEncoding($encoder->getName()); + $this->notifyEncoderChanged($encoder); + + return $this; + } + + /** + * Get the boundary used to separate children in this entity. + * + * @return string + */ + public function getBoundary() + { + if (!isset($this->boundary)) { + $this->boundary = '_=_swift_'.time().'_'.bin2hex(random_bytes(16)).'_=_'; + } + + return $this->boundary; + } + + /** + * Set the boundary used to separate children in this entity. + * + * @param string $boundary + * + * @throws Swift_RfcComplianceException + * + * @return $this + */ + public function setBoundary($boundary) + { + $this->assertValidBoundary($boundary); + $this->boundary = $boundary; + + return $this; + } + + /** + * Receive notification that the charset of this entity, or a parent entity + * has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->notifyCharsetChanged($charset); + } + + /** + * Receive notification that the encoder of this entity or a parent entity + * has changed. + */ + public function encoderChanged(Swift_Mime_ContentEncoder $encoder) + { + $this->notifyEncoderChanged($encoder); + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + public function toString() + { + $string = $this->headers->toString(); + $string .= $this->bodyToString(); + + return $string; + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + protected function bodyToString() + { + $string = ''; + + if (isset($this->body) && empty($this->immediateChildren)) { + if ($this->cache->hasKey($this->cacheKey, 'body')) { + $body = $this->cache->getString($this->cacheKey, 'body'); + } else { + $body = "\r\n".$this->encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength()); + $this->cache->setString($this->cacheKey, 'body', $body, Swift_KeyCache::MODE_WRITE); + } + $string .= $body; + } + + if (!empty($this->immediateChildren)) { + foreach ($this->immediateChildren as $child) { + $string .= "\r\n\r\n--".$this->getBoundary()."\r\n"; + $string .= $child->toString(); + } + $string .= "\r\n\r\n--".$this->getBoundary()."--\r\n"; + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this entire entity to a {@see Swift_InputByteStream}. + */ + public function toByteStream(Swift_InputByteStream $is) + { + $is->write($this->headers->toString()); + $is->commit(); + + $this->bodyToByteStream($is); + } + + /** + * Write this entire entity to a {@link Swift_InputByteStream}. + */ + protected function bodyToByteStream(Swift_InputByteStream $is) + { + if (empty($this->immediateChildren)) { + if (isset($this->body)) { + if ($this->cache->hasKey($this->cacheKey, 'body')) { + $this->cache->exportToByteStream($this->cacheKey, 'body', $is); + } else { + $cacheIs = $this->cache->getInputByteStream($this->cacheKey, 'body'); + if ($cacheIs) { + $is->bind($cacheIs); + } + + $is->write("\r\n"); + + if ($this->body instanceof Swift_OutputByteStream) { + $this->body->setReadPointer(0); + + $this->encoder->encodeByteStream($this->body, $is, 0, $this->getMaxLineLength()); + } else { + $is->write($this->encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength())); + } + + if ($cacheIs) { + $is->unbind($cacheIs); + } + } + } + } + + if (!empty($this->immediateChildren)) { + foreach ($this->immediateChildren as $child) { + $is->write("\r\n\r\n--".$this->getBoundary()."\r\n"); + $child->toByteStream($is); + } + $is->write("\r\n\r\n--".$this->getBoundary()."--\r\n"); + } + } + + /** + * Get the name of the header that provides the ID of this entity. + */ + protected function getIdField() + { + return 'Content-ID'; + } + + /** + * Get the model data (usually an array or a string) for $field. + */ + protected function getHeaderFieldModel($field) + { + if ($this->headers->has($field)) { + return $this->headers->get($field)->getFieldBodyModel(); + } + } + + /** + * Set the model data for $field. + */ + protected function setHeaderFieldModel($field, $model) + { + if ($this->headers->has($field)) { + $this->headers->get($field)->setFieldBodyModel($model); + + return true; + } + + return false; + } + + /** + * Get the parameter value of $parameter on $field header. + */ + protected function getHeaderParameter($field, $parameter) + { + if ($this->headers->has($field)) { + return $this->headers->get($field)->getParameter($parameter); + } + } + + /** + * Set the parameter value of $parameter on $field header. + */ + protected function setHeaderParameter($field, $parameter, $value) + { + if ($this->headers->has($field)) { + $this->headers->get($field)->setParameter($parameter, $value); + + return true; + } + + return false; + } + + /** + * Re-evaluate what content type and encoding should be used on this entity. + */ + protected function fixHeaders() + { + if (\count($this->immediateChildren)) { + $this->setHeaderParameter('Content-Type', 'boundary', + $this->getBoundary() + ); + $this->headers->remove('Content-Transfer-Encoding'); + } else { + $this->setHeaderParameter('Content-Type', 'boundary', null); + $this->setEncoding($this->encoder->getName()); + } + } + + /** + * Get the KeyCache used in this entity. + * + * @return Swift_KeyCache + */ + protected function getCache() + { + return $this->cache; + } + + /** + * Get the ID generator. + * + * @return Swift_IdGenerator + */ + protected function getIdGenerator() + { + return $this->idGenerator; + } + + /** + * Empty the KeyCache for this entity. + */ + protected function clearCache() + { + $this->cache->clearKey($this->cacheKey, 'body'); + } + + private function readStream(Swift_OutputByteStream $os) + { + $string = ''; + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + $os->setReadPointer(0); + + return $string; + } + + private function setEncoding($encoding) + { + if (!$this->setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) { + $this->headers->addTextHeader('Content-Transfer-Encoding', $encoding); + } + } + + private function assertValidBoundary($boundary) + { + if (!preg_match('/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', $boundary)) { + throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.'); + } + } + + private function setContentTypeInHeaders($type) + { + if (!$this->setHeaderFieldModel('Content-Type', $type)) { + $this->headers->addParameterizedHeader('Content-Type', $type); + } + } + + private function setNestingLevel($level) + { + $this->nestingLevel = $level; + } + + private function getCompoundLevel($children) + { + $level = 0; + foreach ($children as $child) { + $level |= $child->getNestingLevel(); + } + + return $level; + } + + private function getNeededChildLevel($child, $compoundLevel) + { + $filter = []; + foreach ($this->compoundLevelFilters as $bitmask => $rules) { + if (($compoundLevel & $bitmask) === $bitmask) { + $filter = $rules + $filter; + } + } + + $realLevel = $child->getNestingLevel(); + $lowercaseType = strtolower($child->getContentType()); + + if (isset($filter[$realLevel]) && isset($filter[$realLevel][$lowercaseType])) { + return $filter[$realLevel][$lowercaseType]; + } + + return $realLevel; + } + + private function createChild() + { + return new self($this->headers->newInstance(), $this->encoder, $this->cache, $this->idGenerator); + } + + private function notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder) + { + foreach ($this->immediateChildren as $child) { + $child->encoderChanged($encoder); + } + } + + private function notifyCharsetChanged($charset) + { + $this->encoder->charsetChanged($charset); + $this->headers->charsetChanged($charset); + foreach ($this->immediateChildren as $child) { + $child->charsetChanged($charset); + } + } + + private function sortChildren() + { + $shouldSort = false; + foreach ($this->immediateChildren as $child) { + // NOTE: This include alternative parts moved into a related part + if (self::LEVEL_ALTERNATIVE == $child->getNestingLevel()) { + $shouldSort = true; + break; + } + } + + // Sort in order of preference, if there is one + if ($shouldSort) { + // Group the messages by order of preference + $sorted = []; + foreach ($this->immediateChildren as $child) { + $type = $child->getContentType(); + $level = \array_key_exists($type, $this->alternativePartOrder) ? $this->alternativePartOrder[$type] : max($this->alternativePartOrder) + 1; + + if (empty($sorted[$level])) { + $sorted[$level] = []; + } + + $sorted[$level][] = $child; + } + + ksort($sorted); + + $this->immediateChildren = array_reduce($sorted, 'array_merge', []); + } + } + + /** + * Empties it's own contents from the cache. + */ + public function __destruct() + { + if ($this->cache instanceof Swift_KeyCache) { + $this->cache->clearAll($this->cacheKey); + } + } + + /** + * Make a deep copy of object. + */ + public function __clone() + { + $this->headers = clone $this->headers; + $this->encoder = clone $this->encoder; + $this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values + $children = []; + foreach ($this->children as $pos => $child) { + $children[$pos] = clone $child; + } + $this->setChildren($children); + } + + public function __wakeup() + { + $this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values + $this->cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream()); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php new file mode 100644 index 0000000..ea97619 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php @@ -0,0 +1,45 @@ +createDependenciesFor('mime.part') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php new file mode 100644 index 0000000..e44b7af --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @author Fabien Potencier + */ +class Swift_NullTransport extends Swift_Transport_NullTransport +{ + public function __construct() + { + \call_user_func_array( + [$this, 'Swift_Transport_NullTransport::__construct'], + Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.null') + ); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php new file mode 100644 index 0000000..1f26f9b --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php @@ -0,0 +1,46 @@ +setThreshold($threshold); + $this->setSleepTime($sleep); + $this->sleeper = $sleeper; + } + + /** + * Set the number of emails to send before restarting. + * + * @param int $threshold + */ + public function setThreshold($threshold) + { + $this->threshold = $threshold; + } + + /** + * Get the number of emails to send before restarting. + * + * @return int + */ + public function getThreshold() + { + return $this->threshold; + } + + /** + * Set the number of seconds to sleep for during a restart. + * + * @param int $sleep time + */ + public function setSleepTime($sleep) + { + $this->sleep = $sleep; + } + + /** + * Get the number of seconds to sleep for during a restart. + * + * @return int + */ + public function getSleepTime() + { + return $this->sleep; + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + ++$this->counter; + if ($this->counter >= $this->threshold) { + $transport = $evt->getTransport(); + $transport->stop(); + if ($this->sleep) { + $this->sleep($this->sleep); + } + $transport->start(); + $this->counter = 0; + } + } + + /** + * Sleep for $seconds. + * + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->sleeper)) { + $this->sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php new file mode 100644 index 0000000..36451f4 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php @@ -0,0 +1,154 @@ +getMessage(); + $message->toByteStream($this); + } + + /** + * Invoked immediately following a command being sent. + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->out += \strlen($command); + } + + /** + * Invoked immediately following a response coming back. + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->in += \strlen($response); + } + + /** + * Called when a message is sent so that the outgoing counter can be increased. + * + * @param string $bytes + */ + public function write($bytes) + { + $this->out += \strlen($bytes); + foreach ($this->mirrors as $stream) { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + */ + public function bind(Swift_InputByteStream $is) + { + $this->mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->mirrors as $k => $stream) { + if ($is === $stream) { + unset($this->mirrors[$k]); + } + } + } + + /** + * Not used. + */ + public function flushBuffers() + { + foreach ($this->mirrors as $stream) { + $stream->flushBuffers(); + } + } + + /** + * Get the total number of bytes sent to the server. + * + * @return int + */ + public function getBytesOut() + { + return $this->out; + } + + /** + * Get the total number of bytes received from the server. + * + * @return int + */ + public function getBytesIn() + { + return $this->in; + } + + /** + * Reset the internal counters to zero. + */ + public function reset() + { + $this->out = 0; + $this->in = 0; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php new file mode 100644 index 0000000..9f9f08b --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php @@ -0,0 +1,31 @@ + + * $replacements = array( + * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"), + * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y") + * ) + * + * + * When using an instance of {@link Swift_Plugins_Decorator_Replacements}, + * the object should return just the array of replacements for the address + * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + */ + public function __construct($replacements) + { + $this->setReplacements($replacements); + } + + /** + * Sets replacements. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + * + * @see __construct() + */ + public function setReplacements($replacements) + { + if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) { + $this->replacements = (array) $replacements; + } else { + $this->replacements = $replacements; + } + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $this->restoreMessage($message); + $to = array_keys($message->getTo()); + $address = array_shift($to); + if ($replacements = $this->getReplacementsFor($address)) { + $body = $message->getBody(); + $search = array_keys($replacements); + $replace = array_values($replacements); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $this->originalBody = $body; + $message->setBody($bodyReplaced); + } + + foreach ($message->getHeaders()->getAll() as $header) { + $body = $header->getFieldBodyModel(); + $count = 0; + if (\is_array($body)) { + $bodyReplaced = []; + foreach ($body as $key => $value) { + $count1 = 0; + $count2 = 0; + $key = \is_string($key) ? str_replace($search, $replace, $key, $count1) : $key; + $value = \is_string($value) ? str_replace($search, $replace, $value, $count2) : $value; + $bodyReplaced[$key] = $value; + + if (!$count && ($count1 || $count2)) { + $count = 1; + } + } + } elseif (\is_string($body)) { + $bodyReplaced = str_replace($search, $replace, $body, $count); + } + + if ($count) { + $this->originalHeaders[$header->getFieldName()] = $body; + $header->setFieldBodyModel($bodyReplaced); + } + } + + $children = (array) $message->getChildren(); + foreach ($children as $child) { + list($type) = sscanf($child->getContentType(), '%[^/]/%s'); + if ('text' == $type) { + $body = $child->getBody(); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $child->setBody($bodyReplaced); + $this->originalChildBodies[$child->getId()] = $body; + } + } + } + $this->lastMessage = $message; + } + } + + /** + * Find a map of replacements for the address. + * + * If this plugin was provided with a delegate instance of + * {@link Swift_Plugins_Decorator_Replacements} then the call will be + * delegated to it. Otherwise, it will attempt to find the replacements + * from the array provided in the constructor. + * + * If no replacements can be found, an empty value (NULL) is returned. + * + * @param string $address + * + * @return array + */ + public function getReplacementsFor($address) + { + if ($this->replacements instanceof Swift_Plugins_Decorator_Replacements) { + return $this->replacements->getReplacementsFor($address); + } + + return $this->replacements[$address] ?? null; + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->restoreMessage($evt->getMessage()); + } + + /** Restore a changed message back to its original state */ + private function restoreMessage(Swift_Mime_SimpleMessage $message) + { + if ($this->lastMessage === $message) { + if (isset($this->originalBody)) { + $message->setBody($this->originalBody); + $this->originalBody = null; + } + if (!empty($this->originalHeaders)) { + foreach ($message->getHeaders()->getAll() as $header) { + if (\array_key_exists($header->getFieldName(), $this->originalHeaders)) { + $header->setFieldBodyModel($this->originalHeaders[$header->getFieldName()]); + } + } + $this->originalHeaders = []; + } + if (!empty($this->originalChildBodies)) { + $children = (array) $message->getChildren(); + foreach ($children as $child) { + $id = $child->getId(); + if (\array_key_exists($id, $this->originalChildBodies)) { + $child->setBody($this->originalChildBodies[$id]); + } + } + $this->originalChildBodies = []; + } + $this->lastMessage = null; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php new file mode 100644 index 0000000..3f4dbbf --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php @@ -0,0 +1,65 @@ +sender = $sender; + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // save current recipients + $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath()); + + // replace them with the one to send to + $message->setReturnPath($this->sender); + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-Return-Path')) { + $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress()); + $headers->removeAll('X-Swift-Return-Path'); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php new file mode 100644 index 0000000..d9bce89 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php @@ -0,0 +1,36 @@ +logger = $logger; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->logger->add($entry); + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->logger->clear(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return $this->logger->dump(); + } + + /** + * Invoked immediately following a command being sent. + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->logger->add(sprintf('>> %s', $command)); + } + + /** + * Invoked immediately following a response coming back. + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->logger->add(sprintf('<< %s', $response)); + } + + /** + * Invoked just before a Transport is started. + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = \get_class($evt->getSource()); + $this->logger->add(sprintf('++ Starting %s', $transportName)); + } + + /** + * Invoked immediately after the Transport is started. + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = \get_class($evt->getSource()); + $this->logger->add(sprintf('++ %s started', $transportName)); + } + + /** + * Invoked just before a Transport is stopped. + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = \get_class($evt->getSource()); + $this->logger->add(sprintf('++ Stopping %s', $transportName)); + } + + /** + * Invoked immediately after the Transport is stopped. + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = \get_class($evt->getSource()); + $this->logger->add(sprintf('++ %s stopped', $transportName)); + } + + /** + * Invoked as a TransportException is thrown in the Transport system. + */ + public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt) + { + $e = $evt->getException(); + $message = $e->getMessage(); + $code = $e->getCode(); + $this->logger->add(sprintf('!! %s (code: %s)', $message, $code)); + $message .= PHP_EOL; + $message .= 'Log data:'.PHP_EOL; + $message .= $this->logger->dump(); + $evt->cancelBubble(); + throw new Swift_TransportException($message, $code, $e->getPrevious()); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php new file mode 100644 index 0000000..6f595ad --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php @@ -0,0 +1,72 @@ +size = $size; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->log[] = $entry; + while (\count($this->log) > $this->size) { + array_shift($this->log); + } + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->log = []; + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return implode(PHP_EOL, $this->log); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php new file mode 100644 index 0000000..40a53d2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php @@ -0,0 +1,58 @@ +isHtml = $isHtml; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + if ($this->isHtml) { + printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '
    ', PHP_EOL); + } else { + printf('%s%s', $entry, PHP_EOL); + } + } + + /** + * Not implemented. + */ + public function clear() + { + } + + /** + * Not implemented. + */ + public function dump() + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php new file mode 100644 index 0000000..39c48ed --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php @@ -0,0 +1,70 @@ +messages = []; + } + + /** + * Get the message list. + * + * @return Swift_Mime_SimpleMessage[] + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get the message count. + * + * @return int count + */ + public function countMessages() + { + return \count($this->messages); + } + + /** + * Empty the message list. + */ + public function clear() + { + $this->messages = []; + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $this->messages[] = clone $evt->getMessage(); + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php new file mode 100644 index 0000000..fb99e4c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php @@ -0,0 +1,31 @@ +host = $host; + $this->port = $port; + $this->crypto = $crypto; + } + + /** + * Set a Pop3Connection to delegate to instead of connecting directly. + * + * @return $this + */ + public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Bind this plugin to a specific SMTP transport instance. + */ + public function bindSmtp(Swift_Transport $smtp) + { + $this->transport = $smtp; + } + + /** + * Set the connection timeout in seconds (default 10). + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Set the username to use when connecting (if needed). + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->username = $username; + + return $this; + } + + /** + * Set the password to use when connecting (if needed). + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Connect to the POP3 host and authenticate. + * + * @throws Swift_Plugins_Pop_Pop3Exception if connection fails + */ + public function connect() + { + if (isset($this->connection)) { + $this->connection->connect(); + } else { + if (!isset($this->socket)) { + if (!$socket = fsockopen( + $this->getHostString(), $this->port, $errno, $errstr, $this->timeout)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('Failed to connect to POP3 host [%s]: %s', $this->host, $errstr)); + } + $this->socket = $socket; + + if (false === $greeting = fgets($this->socket)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('Failed to connect to POP3 host [%s]', trim($greeting))); + } + + $this->assertOk($greeting); + + if ($this->username) { + $this->command(sprintf("USER %s\r\n", $this->username)); + $this->command(sprintf("PASS %s\r\n", $this->password)); + } + } + } + } + + /** + * Disconnect from the POP3 host. + */ + public function disconnect() + { + if (isset($this->connection)) { + $this->connection->disconnect(); + } else { + $this->command("QUIT\r\n"); + if (!fclose($this->socket)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('POP3 host [%s] connection could not be stopped', $this->host)); + } + $this->socket = null; + } + } + + /** + * Invoked just before a Transport is started. + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + if (isset($this->transport)) { + if ($this->transport !== $evt->getTransport()) { + return; + } + } + + $this->connect(); + $this->disconnect(); + } + + /** + * Not used. + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + private function command($command) + { + if (!fwrite($this->socket, $command)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('Failed to write command [%s] to POP3 host', trim($command))); + } + + if (false === $response = fgets($this->socket)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('Failed to read from POP3 host after command [%s]', trim($command))); + } + + $this->assertOk($response); + + return $response; + } + + private function assertOk($response) + { + if ('+OK' != substr($response, 0, 3)) { + throw new Swift_Plugins_Pop_Pop3Exception(sprintf('POP3 command failed [%s]', trim($response))); + } + } + + private function getHostString() + { + $host = $this->host; + switch (strtolower($this->crypto)) { + case 'ssl': + $host = 'ssl://'.$host; + break; + + case 'tls': + $host = 'tls://'.$host; + break; + } + + return $host; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php new file mode 100644 index 0000000..f7373b2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php @@ -0,0 +1,201 @@ +recipient = $recipient; + $this->whitelist = $whitelist; + } + + /** + * Set the recipient of all messages. + * + * @param mixed $recipient + */ + public function setRecipient($recipient) + { + $this->recipient = $recipient; + } + + /** + * Get the recipient of all messages. + * + * @return mixed + */ + public function getRecipient() + { + return $this->recipient; + } + + /** + * Set a list of regular expressions to whitelist certain recipients. + */ + public function setWhitelist(array $whitelist) + { + $this->whitelist = $whitelist; + } + + /** + * Get the whitelist. + * + * @return array + */ + public function getWhitelist() + { + return $this->whitelist; + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // conditionally save current recipients + + if ($headers->has('to')) { + $headers->addMailboxHeader('X-Swift-To', $message->getTo()); + } + + if ($headers->has('cc')) { + $headers->addMailboxHeader('X-Swift-Cc', $message->getCc()); + } + + if ($headers->has('bcc')) { + $headers->addMailboxHeader('X-Swift-Bcc', $message->getBcc()); + } + + // Filter remaining headers against whitelist + $this->filterHeaderSet($headers, 'To'); + $this->filterHeaderSet($headers, 'Cc'); + $this->filterHeaderSet($headers, 'Bcc'); + + // Add each hard coded recipient + $to = $message->getTo(); + if (null === $to) { + $to = []; + } + + foreach ((array) $this->recipient as $recipient) { + if (!\array_key_exists($recipient, $to)) { + $message->addTo($recipient); + } + } + } + + /** + * Filter header set against a whitelist of regular expressions. + * + * @param string $type + */ + private function filterHeaderSet(Swift_Mime_SimpleHeaderSet $headerSet, $type) + { + foreach ($headerSet->getAll($type) as $headers) { + $headers->setNameAddresses($this->filterNameAddresses($headers->getNameAddresses())); + } + } + + /** + * Filtered list of addresses => name pairs. + * + * @return array + */ + private function filterNameAddresses(array $recipients) + { + $filtered = []; + + foreach ($recipients as $address => $name) { + if ($this->isWhitelisted($address)) { + $filtered[$address] = $name; + } + } + + return $filtered; + } + + /** + * Matches address against whitelist of regular expressions. + * + * @return bool + */ + protected function isWhitelisted($recipient) + { + if (\in_array($recipient, (array) $this->recipient)) { + return true; + } + + foreach ($this->whitelist as $pattern) { + if (preg_match($pattern, $recipient)) { + return true; + } + } + + return false; + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->restoreMessage($evt->getMessage()); + } + + private function restoreMessage(Swift_Mime_SimpleMessage $message) + { + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-To')) { + $message->setTo($headers->get('X-Swift-To')->getNameAddresses()); + $headers->removeAll('X-Swift-To'); + } else { + $message->setTo(null); + } + + if ($headers->has('X-Swift-Cc')) { + $message->setCc($headers->get('X-Swift-Cc')->getNameAddresses()); + $headers->removeAll('X-Swift-Cc'); + } + + if ($headers->has('X-Swift-Bcc')) { + $message->setBcc($headers->get('X-Swift-Bcc')->getNameAddresses()); + $headers->removeAll('X-Swift-Bcc'); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php new file mode 100644 index 0000000..b881833 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php @@ -0,0 +1,31 @@ +reporter = $reporter; + } + + /** + * Not used. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $failures = array_flip($evt->getFailedRecipients()); + foreach ((array) $message->getTo() as $address => $null) { + $this->reporter->notify($message, $address, (\array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS)); + } + foreach ((array) $message->getCc() as $address => $null) { + $this->reporter->notify($message, $address, (\array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS)); + } + foreach ((array) $message->getBcc() as $address => $null) { + $this->reporter->notify($message, $address, (\array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS)); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php new file mode 100644 index 0000000..249cffb --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php @@ -0,0 +1,58 @@ +failures_cache[$address])) { + $this->failures[] = $address; + $this->failures_cache[$address] = true; + } + } + + /** + * Get an array of addresses for which delivery failed. + * + * @return array + */ + public function getFailedRecipients() + { + return $this->failures; + } + + /** + * Clear the buffer (empty the list). + */ + public function clear() + { + $this->failures = $this->failures_cache = []; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php new file mode 100644 index 0000000..1cfc3f9 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php @@ -0,0 +1,38 @@ +'.PHP_EOL; + echo 'PASS '.$address.PHP_EOL; + echo ''.PHP_EOL; + flush(); + } else { + echo '
    '.PHP_EOL; + echo 'FAIL '.$address.PHP_EOL; + echo '
    '.PHP_EOL; + flush(); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php new file mode 100644 index 0000000..595c0f6 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php @@ -0,0 +1,24 @@ +rate = $rate; + $this->mode = $mode; + $this->sleeper = $sleeper; + $this->timer = $timer; + } + + /** + * Invoked immediately before the Message is sent. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $time = $this->getTimestamp(); + if (!isset($this->start)) { + $this->start = $time; + } + $duration = $time - $this->start; + + switch ($this->mode) { + case self::BYTES_PER_MINUTE: + $sleep = $this->throttleBytesPerMinute($duration); + break; + case self::MESSAGES_PER_SECOND: + $sleep = $this->throttleMessagesPerSecond($duration); + break; + case self::MESSAGES_PER_MINUTE: + $sleep = $this->throttleMessagesPerMinute($duration); + break; + default: + $sleep = 0; + break; + } + + if ($sleep > 0) { + $this->sleep($sleep); + } + } + + /** + * Invoked when a Message is sent. + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + parent::sendPerformed($evt); + ++$this->messages; + } + + /** + * Sleep for $seconds. + * + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->sleeper)) { + $this->sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + public function getTimestamp() + { + if (isset($this->timer)) { + return $this->timer->getTimestamp(); + } + + return time(); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function throttleBytesPerMinute($timePassed) + { + $expectedDuration = $this->getBytesOut() / ($this->rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function throttleMessagesPerSecond($timePassed) + { + $expectedDuration = $this->messages / $this->rate; + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function throttleMessagesPerMinute($timePassed) + { + $expectedDuration = $this->messages / ($this->rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php new file mode 100644 index 0000000..9c8deb3 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php @@ -0,0 +1,24 @@ +register('properties.charset')->asValue($charset); + + return $this; + } + + /** + * Set the directory where temporary files can be saved. + * + * @param string $dir + * + * @return $this + */ + public function setTempDir($dir) + { + Swift_DependencyContainer::getInstance()->register('tempdir')->asValue($dir); + + return $this; + } + + /** + * Set the type of cache to use (i.e. "disk" or "array"). + * + * @param string $type + * + * @return $this + */ + public function setCacheType($type) + { + Swift_DependencyContainer::getInstance()->register('cache')->asAliasOf(sprintf('cache.%s', $type)); + + return $this; + } + + /** + * Set the QuotedPrintable dot escaper preference. + * + * @param bool $dotEscape + * + * @return $this + */ + public function setQPDotEscape($dotEscape) + { + $dotEscape = !empty($dotEscape); + Swift_DependencyContainer::getInstance() + ->register('mime.qpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(['mime.charstream', 'mime.bytecanonicalizer']) + ->addConstructorValue($dotEscape); + + return $this; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php new file mode 100644 index 0000000..2897474 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php @@ -0,0 +1,27 @@ +createDependenciesFor('transport.sendmail') + ); + + $this->setCommand($command); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php new file mode 100644 index 0000000..26c5e28 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php @@ -0,0 +1,19 @@ + + */ +interface Swift_Signer +{ + public function reset(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php new file mode 100644 index 0000000..b25c427 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php @@ -0,0 +1,31 @@ + + */ +interface Swift_Signers_BodySigner extends Swift_Signer +{ + /** + * Change the Swift_Signed_Message to apply the singing. + * + * @return self + */ + public function signMessage(Swift_Message $message); + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php new file mode 100644 index 0000000..9a26abd --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php @@ -0,0 +1,682 @@ + + */ +class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey. + * + * @var string + */ + protected $privateKey; + + /** + * DomainName. + * + * @var string + */ + protected $domainName; + + /** + * Selector. + * + * @var string + */ + protected $selector; + + private $passphrase = ''; + + /** + * Hash algorithm used. + * + * @see RFC6376 3.3: Signers MUST implement and SHOULD sign using rsa-sha256. + * + * @var string + */ + protected $hashAlgorithm = 'rsa-sha256'; + + /** + * Body canon method. + * + * @var string + */ + protected $bodyCanon = 'simple'; + + /** + * Header canon method. + * + * @var string + */ + protected $headerCanon = 'simple'; + + /** + * Headers not being signed. + * + * @var array + */ + protected $ignoredHeaders = ['return-path' => true]; + + /** + * Signer identity. + * + * @var string + */ + protected $signerIdentity; + + /** + * BodyLength. + * + * @var int + */ + protected $bodyLen = 0; + + /** + * Maximum signedLen. + * + * @var int + */ + protected $maxLen = PHP_INT_MAX; + + /** + * Embbed bodyLen in signature. + * + * @var bool + */ + protected $showLen = false; + + /** + * When the signature has been applied (true means time()), false means not embedded. + * + * @var mixed + */ + protected $signatureTimestamp = true; + + /** + * When will the signature expires false means not embedded, if sigTimestamp is auto + * Expiration is relative, otherwise it's absolute. + * + * @var int + */ + protected $signatureExpiration = false; + + /** + * Must we embed signed headers? + * + * @var bool + */ + protected $debugHeaders = false; + + // work variables + /** + * Headers used to generate hash. + * + * @var array + */ + protected $signedHeaders = []; + + /** + * If debugHeaders is set store debugData here. + * + * @var string[] + */ + private $debugHeadersData = []; + + /** + * Stores the bodyHash. + * + * @var string + */ + private $bodyHash = ''; + + /** + * Stores the signature header. + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $dkimHeader; + + private $bodyHashHandler; + + private $headerHash; + + private $headerCanonData = ''; + + private $bodyCanonEmptyCounter = 0; + + private $bodyCanonIgnoreStart = 2; + + private $bodyCanonSpace = false; + + private $bodyCanonLastChar = null; + + private $bodyCanonLine = ''; + + private $bound = []; + + /** + * Constructor. + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + * @param string $passphrase + */ + public function __construct($privateKey, $domainName, $selector, $passphrase = '') + { + $this->privateKey = $privateKey; + $this->domainName = $domainName; + $this->signerIdentity = '@'.$domainName; + $this->selector = $selector; + $this->passphrase = $passphrase; + } + + /** + * Reset the Signer. + * + * @see Swift_Signer::reset() + */ + public function reset() + { + $this->headerHash = null; + $this->signedHeaders = []; + $this->bodyHash = null; + $this->bodyHashHandler = null; + $this->bodyCanonIgnoreStart = 2; + $this->bodyCanonEmptyCounter = 0; + $this->bodyCanonLastChar = null; + $this->bodyCanonSpace = false; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * + * @return int + * + * @throws Swift_IoException + */ + // TODO fix return + public function write($bytes) + { + $this->canonicalizeBody($bytes); + foreach ($this->bound as $is) { + $is->write($bytes); + } + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + */ + public function commit() + { + // Nothing to do + return; + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + */ + public function bind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + $this->bound[] = $is; + + return; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + */ + public function unbind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + foreach ($this->bound as $k => $stream) { + if ($stream === $is) { + unset($this->bound[$k]); + + return; + } + } + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + */ + public function flushBuffers() + { + $this->reset(); + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1. + * + * @param string $hash 'rsa-sha1' or 'rsa-sha256' + * + * @throws Swift_SwiftException + * + * @return $this + */ + public function setHashAlgorithm($hash) + { + switch ($hash) { + case 'rsa-sha1': + $this->hashAlgorithm = 'rsa-sha1'; + break; + case 'rsa-sha256': + $this->hashAlgorithm = 'rsa-sha256'; + if (!\defined('OPENSSL_ALGO_SHA256')) { + throw new Swift_SwiftException('Unable to set sha256 as it is not supported by OpenSSL.'); + } + break; + default: + throw new Swift_SwiftException('Unable to set the hash algorithm, must be one of rsa-sha1 or rsa-sha256 (%s given).', $hash); + } + + return $this; + } + + /** + * Set the body canonicalization algorithm. + * + * @param string $canon + * + * @return $this + */ + public function setBodyCanon($canon) + { + if ('relaxed' == $canon) { + $this->bodyCanon = 'relaxed'; + } else { + $this->bodyCanon = 'simple'; + } + + return $this; + } + + /** + * Set the header canonicalization algorithm. + * + * @param string $canon + * + * @return $this + */ + public function setHeaderCanon($canon) + { + if ('relaxed' == $canon) { + $this->headerCanon = 'relaxed'; + } else { + $this->headerCanon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity. + * + * @param string $identity + * + * @return $this + */ + public function setSignerIdentity($identity) + { + $this->signerIdentity = $identity; + + return $this; + } + + /** + * Set the length of the body to sign. + * + * @param mixed $len (bool or int) + * + * @return $this + */ + public function setBodySignedLen($len) + { + if (true === $len) { + $this->showLen = true; + $this->maxLen = PHP_INT_MAX; + } elseif (false === $len) { + $this->showLen = false; + $this->maxLen = PHP_INT_MAX; + } else { + $this->showLen = true; + $this->maxLen = (int) $len; + } + + return $this; + } + + /** + * Set the signature timestamp. + * + * @param int $time A timestamp + * + * @return $this + */ + public function setSignatureTimestamp($time) + { + $this->signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp. + * + * @param int $time A timestamp + * + * @return $this + */ + public function setSignatureExpiration($time) + { + $this->signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders. + * + * @param bool $debug + * + * @return Swift_Signers_DKIMSigner + */ + public function setDebugHeaders($debug) + { + $this->debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body. + */ + public function startBody() + { + // Init + switch ($this->hashAlgorithm) { + case 'rsa-sha256': + $this->bodyHashHandler = hash_init('sha256'); + break; + case 'rsa-sha1': + $this->bodyHashHandler = hash_init('sha1'); + break; + } + $this->bodyCanonLine = ''; + } + + /** + * End Body. + */ + public function endBody() + { + $this->endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin. + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->debugHeaders) { + return ['DKIM-Signature', 'X-DebugHash']; + } else { + return ['DKIM-Signature']; + } + } + + /** + * Adds an ignored Header. + * + * @param string $header_name + * + * @return Swift_Signers_DKIMSigner + */ + public function ignoreHeader($header_name) + { + $this->ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign. + * + * @return Swift_Signers_DKIMSigner + */ + public function setHeaders(Swift_Mime_SimpleHeaderSet $headers) + { + $this->headerCanonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (!isset($this->ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ('' != $header->getFieldBody()) { + $this->addHeader($header->toString()); + $this->signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + + return $this; + } + + /** + * Add the signature to the given Headers. + * + * @return Swift_Signers_DKIMSigner + */ + public function addSignature(Swift_Mime_SimpleHeaderSet $headers) + { + // Prepare the DKIM-Signature + $params = ['v' => '1', 'a' => $this->hashAlgorithm, 'bh' => base64_encode($this->bodyHash), 'd' => $this->domainName, 'h' => implode(': ', $this->signedHeaders), 'i' => $this->signerIdentity, 's' => $this->selector]; + if ('simple' != $this->bodyCanon) { + $params['c'] = $this->headerCanon.'/'.$this->bodyCanon; + } elseif ('simple' != $this->headerCanon) { + $params['c'] = $this->headerCanon; + } + if ($this->showLen) { + $params['l'] = $this->bodyLen; + } + if (true === $this->signatureTimestamp) { + $params['t'] = time(); + if (false !== $this->signatureExpiration) { + $params['x'] = $params['t'] + $this->signatureExpiration; + } + } else { + if (false !== $this->signatureTimestamp) { + $params['t'] = $this->signatureTimestamp; + } + if (false !== $this->signatureExpiration) { + $params['x'] = $this->signatureExpiration; + } + } + if ($this->debugHeaders) { + $params['z'] = implode('|', $this->debugHeadersData); + } + $string = ''; + foreach ($params as $k => $v) { + $string .= $k.'='.$v.'; '; + } + $string = trim($string); + $headers->addTextHeader('DKIM-Signature', $string); + // Add the last DKIM-Signature + $tmp = $headers->getAll('DKIM-Signature'); + $this->dkimHeader = end($tmp); + $this->addHeader(trim($this->dkimHeader->toString())."\r\n b=", true); + if ($this->debugHeaders) { + $headers->addTextHeader('X-DebugHash', base64_encode($this->headerHash)); + } + $this->dkimHeader->setValue($string.' b='.trim(chunk_split(base64_encode($this->getEncryptedHash()), 73, ' '))); + + return $this; + } + + /* Private helpers */ + + protected function addHeader($header, $is_sig = false) + { + switch ($this->headerCanon) { + case 'relaxed': + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", '', $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", ' ', $value); + $header = $name.':'.trim($value).($is_sig ? '' : "\r\n"); + // no break + case 'simple': + // Nothing to do + } + $this->addToHeaderHash($header); + } + + protected function canonicalizeBody($string) + { + $len = \strlen($string); + $canon = ''; + $method = ('relaxed' == $this->bodyCanon); + for ($i = 0; $i < $len; ++$i) { + if ($this->bodyCanonIgnoreStart > 0) { + --$this->bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r": + $this->bodyCanonLastChar = "\r"; + break; + case "\n": + if ("\r" == $this->bodyCanonLastChar) { + if ($method) { + $this->bodyCanonSpace = false; + } + if ('' == $this->bodyCanonLine) { + ++$this->bodyCanonEmptyCounter; + } else { + $this->bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + // todo handle it but should never happen + } + break; + case ' ': + case "\t": + if ($method) { + $this->bodyCanonSpace = true; + break; + } + // no break + default: + if ($this->bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->bodyCanonEmptyCounter); + $this->bodyCanonEmptyCounter = 0; + } + if ($this->bodyCanonSpace) { + $this->bodyCanonLine .= ' '; + $canon .= ' '; + $this->bodyCanonSpace = false; + } + $this->bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->addToBodyHash($canon); + } + + protected function endOfBody() + { + // Add trailing Line return if last line is non empty + if (\strlen($this->bodyCanonLine) > 0) { + $this->addToBodyHash("\r\n"); + } + $this->bodyHash = hash_final($this->bodyHashHandler, true); + } + + private function addToBodyHash($string) + { + $len = \strlen($string); + if ($len > ($new_len = ($this->maxLen - $this->bodyLen))) { + $string = substr($string, 0, $new_len); + $len = $new_len; + } + hash_update($this->bodyHashHandler, $string); + $this->bodyLen += $len; + } + + private function addToHeaderHash($header) + { + if ($this->debugHeaders) { + $this->debugHeadersData[] = trim($header); + } + $this->headerCanonData .= $header; + } + + /** + * @throws Swift_SwiftException + * + * @return string + */ + private function getEncryptedHash() + { + $signature = ''; + switch ($this->hashAlgorithm) { + case 'rsa-sha1': + $algorithm = OPENSSL_ALGO_SHA1; + break; + case 'rsa-sha256': + $algorithm = OPENSSL_ALGO_SHA256; + break; + } + $pkeyId = openssl_get_privatekey($this->privateKey, $this->passphrase); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->headerCanonData, $signature, $pkeyId, $algorithm)) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']'); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php new file mode 100644 index 0000000..8833765 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php @@ -0,0 +1,504 @@ + + */ +class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey. + * + * @var string + */ + protected $privateKey; + + /** + * DomainName. + * + * @var string + */ + protected $domainName; + + /** + * Selector. + * + * @var string + */ + protected $selector; + + /** + * Hash algorithm used. + * + * @var string + */ + protected $hashAlgorithm = 'rsa-sha1'; + + /** + * Canonisation method. + * + * @var string + */ + protected $canon = 'simple'; + + /** + * Headers not being signed. + * + * @var array + */ + protected $ignoredHeaders = []; + + /** + * Signer identity. + * + * @var string + */ + protected $signerIdentity; + + /** + * Must we embed signed headers? + * + * @var bool + */ + protected $debugHeaders = false; + + // work variables + /** + * Headers used to generate hash. + * + * @var array + */ + private $signedHeaders = []; + + /** + * Stores the signature header. + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $domainKeyHeader; + + /** + * Hash Handler. + * + * @var resource|null + */ + private $hashHandler; + + private $canonData = ''; + + private $bodyCanonEmptyCounter = 0; + + private $bodyCanonIgnoreStart = 2; + + private $bodyCanonSpace = false; + + private $bodyCanonLastChar = null; + + private $bodyCanonLine = ''; + + private $bound = []; + + /** + * Constructor. + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + */ + public function __construct($privateKey, $domainName, $selector) + { + $this->privateKey = $privateKey; + $this->domainName = $domainName; + $this->signerIdentity = '@'.$domainName; + $this->selector = $selector; + } + + /** + * Resets internal states. + * + * @return $this + */ + public function reset() + { + $this->hashHandler = null; + $this->bodyCanonIgnoreStart = 2; + $this->bodyCanonEmptyCounter = 0; + $this->bodyCanonLastChar = null; + $this->bodyCanonSpace = false; + + return $this; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * + * @return int + * + * @throws Swift_IoException + * + * @return $this + */ + public function write($bytes) + { + $this->canonicalizeBody($bytes); + foreach ($this->bound as $is) { + $is->write($bytes); + } + + return $this; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + * + * @return $this + */ + public function commit() + { + // Nothing to do + return $this; + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @return $this + */ + public function bind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + $this->bound[] = $is; + + return $this; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @return $this + */ + public function unbind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + foreach ($this->bound as $k => $stream) { + if ($stream === $is) { + unset($this->bound[$k]); + + break; + } + } + + return $this; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + * + * @return $this + */ + public function flushBuffers() + { + $this->reset(); + + return $this; + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256. + * + * @param string $hash + * + * @return $this + */ + public function setHashAlgorithm($hash) + { + $this->hashAlgorithm = 'rsa-sha1'; + + return $this; + } + + /** + * Set the canonicalization algorithm. + * + * @param string $canon simple | nofws defaults to simple + * + * @return $this + */ + public function setCanon($canon) + { + if ('nofws' == $canon) { + $this->canon = 'nofws'; + } else { + $this->canon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity. + * + * @param string $identity + * + * @return $this + */ + public function setSignerIdentity($identity) + { + $this->signerIdentity = $identity; + + return $this; + } + + /** + * Enable / disable the DebugHeaders. + * + * @param bool $debug + * + * @return $this + */ + public function setDebugHeaders($debug) + { + $this->debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body. + */ + public function startBody() + { + } + + /** + * End Body. + */ + public function endBody() + { + $this->endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin. + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->debugHeaders) { + return ['DomainKey-Signature', 'X-DebugHash']; + } + + return ['DomainKey-Signature']; + } + + /** + * Adds an ignored Header. + * + * @param string $header_name + * + * @return $this + */ + public function ignoreHeader($header_name) + { + $this->ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign. + * + * @return $this + */ + public function setHeaders(Swift_Mime_SimpleHeaderSet $headers) + { + $this->startHash(); + $this->canonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (!isset($this->ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ('' != $header->getFieldBody()) { + $this->addHeader($header->toString()); + $this->signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + $this->endOfHeaders(); + + return $this; + } + + /** + * Add the signature to the given Headers. + * + * @return $this + */ + public function addSignature(Swift_Mime_SimpleHeaderSet $headers) + { + // Prepare the DomainKey-Signature Header + $params = ['a' => $this->hashAlgorithm, 'b' => chunk_split(base64_encode($this->getEncryptedHash()), 73, ' '), 'c' => $this->canon, 'd' => $this->domainName, 'h' => implode(': ', $this->signedHeaders), 'q' => 'dns', 's' => $this->selector]; + $string = ''; + foreach ($params as $k => $v) { + $string .= $k.'='.$v.'; '; + } + $string = trim($string); + $headers->addTextHeader('DomainKey-Signature', $string); + + return $this; + } + + /* Private helpers */ + + protected function addHeader($header) + { + switch ($this->canon) { + case 'nofws': + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", '', $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", ' ', $value); + $header = $name.':'.trim($value)."\r\n"; + // no break + case 'simple': + // Nothing to do + } + $this->addToHash($header); + } + + protected function endOfHeaders() + { + $this->bodyCanonEmptyCounter = 1; + } + + protected function canonicalizeBody($string) + { + $len = \strlen($string); + $canon = ''; + $nofws = ('nofws' == $this->canon); + for ($i = 0; $i < $len; ++$i) { + if ($this->bodyCanonIgnoreStart > 0) { + --$this->bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r": + $this->bodyCanonLastChar = "\r"; + break; + case "\n": + if ("\r" == $this->bodyCanonLastChar) { + if ($nofws) { + $this->bodyCanonSpace = false; + } + if ('' == $this->bodyCanonLine) { + ++$this->bodyCanonEmptyCounter; + } else { + $this->bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r'); + } + break; + case ' ': + case "\t": + case "\x09": //HTAB + if ($nofws) { + $this->bodyCanonSpace = true; + break; + } + // no break + default: + if ($this->bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->bodyCanonEmptyCounter); + $this->bodyCanonEmptyCounter = 0; + } + $this->bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->addToHash($canon); + } + + protected function endOfBody() + { + if (\strlen($this->bodyCanonLine) > 0) { + $this->addToHash("\r\n"); + } + } + + private function addToHash($string) + { + $this->canonData .= $string; + hash_update($this->hashHandler, $string); + } + + private function startHash() + { + // Init + switch ($this->hashAlgorithm) { + case 'rsa-sha1': + $this->hashHandler = hash_init('sha1'); + break; + } + $this->bodyCanonLine = ''; + } + + /** + * @throws Swift_SwiftException + * + * @return string + */ + private function getEncryptedHash() + { + $signature = ''; + $pkeyId = openssl_get_privatekey($this->privateKey); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DomainKey Hash ['.openssl_error_string().']'); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php new file mode 100644 index 0000000..6f5c209 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php @@ -0,0 +1,61 @@ + + */ +interface Swift_Signers_HeaderSigner extends Swift_Signer, Swift_InputByteStream +{ + /** + * Exclude an header from the signed headers. + * + * @param string $header_name + * + * @return self + */ + public function ignoreHeader($header_name); + + /** + * Prepare the Signer to get a new Body. + * + * @return self + */ + public function startBody(); + + /** + * Give the signal that the body has finished streaming. + * + * @return self + */ + public function endBody(); + + /** + * Give the headers already given. + * + * @return self + */ + public function setHeaders(Swift_Mime_SimpleHeaderSet $headers); + + /** + * Add the header(s) to the headerSet. + * + * @return self + */ + public function addSignature(Swift_Mime_SimpleHeaderSet $headers); + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php new file mode 100644 index 0000000..6d7b589 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php @@ -0,0 +1,183 @@ + + * + * @deprecated since SwiftMailer 6.1.0; use Swift_Signers_DKIMSigner instead. + */ +class Swift_Signers_OpenDKIMSigner extends Swift_Signers_DKIMSigner +{ + private $peclLoaded = false; + + private $dkimHandler = null; + + private $dropFirstLF = true; + + const CANON_RELAXED = 1; + const CANON_SIMPLE = 2; + const SIG_RSA_SHA1 = 3; + const SIG_RSA_SHA256 = 4; + + public function __construct($privateKey, $domainName, $selector) + { + if (!\extension_loaded('opendkim')) { + throw new Swift_SwiftException('php-opendkim extension not found'); + } + + $this->peclLoaded = true; + + parent::__construct($privateKey, $domainName, $selector); + } + + public function addSignature(Swift_Mime_SimpleHeaderSet $headers) + { + $header = new Swift_Mime_Headers_OpenDKIMHeader('DKIM-Signature'); + $headerVal = $this->dkimHandler->getSignatureHeader(); + if (false === $headerVal || \is_int($headerVal)) { + throw new Swift_SwiftException('OpenDKIM Error: '.$this->dkimHandler->getError()); + } + $header->setValue($headerVal); + $headers->set($header); + + return $this; + } + + public function setHeaders(Swift_Mime_SimpleHeaderSet $headers) + { + $hash = 'rsa-sha1' == $this->hashAlgorithm ? OpenDKIMSign::ALG_RSASHA1 : OpenDKIMSign::ALG_RSASHA256; + $bodyCanon = 'simple' == $this->bodyCanon ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED; + $headerCanon = 'simple' == $this->headerCanon ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED; + $this->dkimHandler = new OpenDKIMSign($this->privateKey, $this->selector, $this->domainName, $headerCanon, $bodyCanon, $hash, -1); + // Hardcode signature Margin for now + $this->dkimHandler->setMargin(78); + + if (!is_numeric($this->signatureTimestamp)) { + OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, time()); + } else { + if (!OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, $this->signatureTimestamp)) { + throw new Swift_SwiftException('Unable to force signature timestamp ['.openssl_error_string().']'); + } + } + if (isset($this->signerIdentity)) { + $this->dkimHandler->setSigner($this->signerIdentity); + } + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (!isset($this->ignoredHeaders[strtolower($hName)])) { + $tmp = $headers->getAll($hName); + if ($headers->has($hName)) { + foreach ($tmp as $header) { + if ('' != $header->getFieldBody()) { + $htosign = $header->toString(); + $this->dkimHandler->header($htosign); + $this->signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + + return $this; + } + + public function startBody() + { + if (!$this->peclLoaded) { + return parent::startBody(); + } + $this->dropFirstLF = true; + $this->dkimHandler->eoh(); + + return $this; + } + + public function endBody() + { + if (!$this->peclLoaded) { + return parent::endBody(); + } + $this->dkimHandler->eom(); + + return $this; + } + + public function reset() + { + $this->dkimHandler = null; + parent::reset(); + + return $this; + } + + /** + * Set the signature timestamp. + * + * @param int $time + * + * @return $this + */ + public function setSignatureTimestamp($time) + { + $this->signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp. + * + * @param int $time + * + * @return $this + */ + public function setSignatureExpiration($time) + { + $this->signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders. + * + * @param bool $debug + * + * @return $this + */ + public function setDebugHeaders($debug) + { + $this->debugHeaders = (bool) $debug; + + return $this; + } + + // Protected + + protected function canonicalizeBody($string) + { + if (!$this->peclLoaded) { + return parent::canonicalizeBody($string); + } + if (true === $this->dropFirstLF) { + if ("\r" == $string[0] && "\n" == $string[1]) { + $string = substr($string, 2); + } + } + $this->dropFirstLF = false; + if (\strlen($string)) { + $this->dkimHandler->body($string); + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php new file mode 100644 index 0000000..30f3cbd --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php @@ -0,0 +1,542 @@ + + * @author Jan Flora + */ +class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner +{ + protected $signCertificate; + protected $signPrivateKey; + protected $encryptCert; + protected $signThenEncrypt = true; + protected $signLevel; + protected $encryptLevel; + protected $signOptions; + protected $encryptOptions; + protected $encryptCipher; + protected $extraCerts = null; + protected $wrapFullMessage = false; + + /** + * @var Swift_StreamFilters_StringReplacementFilterFactory + */ + protected $replacementFactory; + + /** + * @var Swift_Mime_SimpleHeaderFactory + */ + protected $headerFactory; + + /** + * Constructor. + * + * @param string|null $signCertificate + * @param string|null $signPrivateKey + * @param string|null $encryptCertificate + */ + public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null) + { + if (null !== $signPrivateKey) { + $this->setSignCertificate($signCertificate, $signPrivateKey); + } + + if (null !== $encryptCertificate) { + $this->setEncryptCertificate($encryptCertificate); + } + + $this->replacementFactory = Swift_DependencyContainer::getInstance() + ->lookup('transport.replacementfactory'); + + $this->signOptions = PKCS7_DETACHED; + $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC; + } + + /** + * Set the certificate location to use for signing. + * + * @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php + * + * @param string $certificate + * @param string|array $privateKey If the key needs an passphrase use array('file-location', 'passphrase') instead + * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() + * @param string $extraCerts A file containing intermediate certificates needed by the signing certificate + * + * @return $this + */ + public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null) + { + $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate)); + + if (null !== $privateKey) { + if (\is_array($privateKey)) { + $this->signPrivateKey = $privateKey; + $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0])); + } else { + $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey)); + } + } + + $this->signOptions = $signOptions; + $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; + + return $this; + } + + /** + * Set the certificate location to use for encryption. + * + * @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php + * @see https://secure.php.net/manual/en/openssl.ciphers.php + * + * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates. + * @param int $cipher + * + * @return $this + */ + public function setEncryptCertificate($recipientCerts, $cipher = null) + { + if (\is_array($recipientCerts)) { + $this->encryptCert = []; + + foreach ($recipientCerts as $cert) { + $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert)); + } + } else { + $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts)); + } + + if (null !== $cipher) { + $this->encryptCipher = $cipher; + } + + return $this; + } + + /** + * @return string + */ + public function getSignCertificate() + { + return $this->signCertificate; + } + + /** + * @return string + */ + public function getSignPrivateKey() + { + return $this->signPrivateKey; + } + + /** + * Set perform signing before encryption. + * + * The default is to first sign the message and then encrypt. + * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted. + * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients. + * + * @param bool $signThenEncrypt + * + * @return $this + */ + public function setSignThenEncrypt($signThenEncrypt = true) + { + $this->signThenEncrypt = $signThenEncrypt; + + return $this; + } + + /** + * @return bool + */ + public function isSignThenEncrypt() + { + return $this->signThenEncrypt; + } + + /** + * Resets internal states. + * + * @return $this + */ + public function reset() + { + return $this; + } + + /** + * Specify whether to wrap the entire MIME message in the S/MIME message. + * + * According to RFC5751 section 3.1: + * In order to protect outer, non-content-related message header fields + * (for instance, the "Subject", "To", "From", and "Cc" fields), the + * sending client MAY wrap a full MIME message in a message/rfc822 + * wrapper in order to apply S/MIME security services to these header + * fields. It is up to the receiving client to decide how to present + * this "inner" header along with the unprotected "outer" header. + * + * @param bool $wrap + * + * @return $this + */ + public function setWrapFullMessage($wrap) + { + $this->wrapFullMessage = $wrap; + } + + /** + * Change the Swift_Message to apply the signing. + * + * @return $this + */ + public function signMessage(Swift_Message $message) + { + if (null === $this->signCertificate && null === $this->encryptCert) { + return $this; + } + + if ($this->signThenEncrypt) { + $this->smimeSignMessage($message); + $this->smimeEncryptMessage($message); + } else { + $this->smimeEncryptMessage($message); + $this->smimeSignMessage($message); + } + } + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders() + { + return ['Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition']; + } + + /** + * Sign a Swift message. + */ + protected function smimeSignMessage(Swift_Message $message) + { + // If we don't have a certificate we can't sign the message + if (null === $this->signCertificate) { + return; + } + + // Work on a clone of the original message + $signMessage = clone $message; + $signMessage->clearSigners(); + + if ($this->wrapFullMessage) { + // The original message essentially becomes the body of the new + // wrapped message + $signMessage = $this->wrapMimeMessage($signMessage); + } else { + // Only keep header needed to parse the body correctly + $this->clearAllHeaders($signMessage); + $this->copyHeaders( + $message, + $signMessage, + [ + 'Content-Type', + 'Content-Transfer-Encoding', + 'Content-Disposition', + ] + ); + } + + // Copy the cloned message into a temporary file stream + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $signMessage->toByteStream($messageStream); + $messageStream->commit(); + $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + // Sign the message using openssl + if (!openssl_pkcs7_sign( + $messageStream->getPath(), + $signedMessageStream->getPath(), + $this->signCertificate, + $this->signPrivateKey, + [], + $this->signOptions, + $this->extraCerts + ) + ) { + throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + // Parse the resulting signed message content back into the Swift message + // preserving the original headers + $this->parseSSLOutput($signedMessageStream, $message); + } + + /** + * Encrypt a Swift message. + */ + protected function smimeEncryptMessage(Swift_Message $message) + { + // If we don't have a certificate we can't encrypt the message + if (null === $this->encryptCert) { + return; + } + + // Work on a clone of the original message + $encryptMessage = clone $message; + $encryptMessage->clearSigners(); + + if ($this->wrapFullMessage) { + // The original message essentially becomes the body of the new + // wrapped message + $encryptMessage = $this->wrapMimeMessage($encryptMessage); + } else { + // Only keep header needed to parse the body correctly + $this->clearAllHeaders($encryptMessage); + $this->copyHeaders( + $message, + $encryptMessage, + [ + 'Content-Type', + 'Content-Transfer-Encoding', + 'Content-Disposition', + ] + ); + } + + // Convert the message content (including headers) to a string + // and place it in a temporary file + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $encryptMessage->toByteStream($messageStream); + $messageStream->commit(); + $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + // Encrypt the message + if (!openssl_pkcs7_encrypt( + $messageStream->getPath(), + $encryptedMessageStream->getPath(), + $this->encryptCert, + [], + 0, + $this->encryptCipher + ) + ) { + throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + // Parse the resulting signed message content back into the Swift message + // preserving the original headers + $this->parseSSLOutput($encryptedMessageStream, $message); + } + + /** + * Copy named headers from one Swift message to another. + */ + protected function copyHeaders( + Swift_Message $fromMessage, + Swift_Message $toMessage, + array $headers = [] + ) { + foreach ($headers as $header) { + $this->copyHeader($fromMessage, $toMessage, $header); + } + } + + /** + * Copy a single header from one Swift message to another. + * + * @param string $headerName + */ + protected function copyHeader(Swift_Message $fromMessage, Swift_Message $toMessage, $headerName) + { + $header = $fromMessage->getHeaders()->get($headerName); + if (!$header) { + return; + } + $headers = $toMessage->getHeaders(); + switch ($header->getFieldType()) { + case Swift_Mime_Header::TYPE_TEXT: + $headers->addTextHeader($header->getFieldName(), $header->getValue()); + break; + case Swift_Mime_Header::TYPE_PARAMETERIZED: + $headers->addParameterizedHeader( + $header->getFieldName(), + $header->getValue(), + $header->getParameters() + ); + break; + } + } + + /** + * Remove all headers from a Swift message. + */ + protected function clearAllHeaders(Swift_Message $message) + { + $headers = $message->getHeaders(); + foreach ($headers->listAll() as $header) { + $headers->removeAll($header); + } + } + + /** + * Wraps a Swift_Message in a message/rfc822 MIME part. + * + * @return Swift_MimePart + */ + protected function wrapMimeMessage(Swift_Message $message) + { + // Start by copying the original message into a message stream + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + // Create a new MIME part that wraps the original stream + $wrappedMessage = new Swift_MimePart($messageStream, 'message/rfc822'); + $wrappedMessage->setEncoder(new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit')); + + return $wrappedMessage; + } + + protected function parseSSLOutput(Swift_InputByteStream $inputStream, Swift_Message $message) + { + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $this->copyFromOpenSSLOutput($inputStream, $messageStream); + + $this->streamToMime($messageStream, $message); + } + + /** + * Merges an OutputByteStream from OpenSSL to a Swift_Message. + */ + protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message) + { + // Parse the stream into headers and body + list($headers, $messageStream) = $this->parseStream($fromStream); + + // Get the original message headers + $messageHeaders = $message->getHeaders(); + + // Let the stream determine the headers describing the body content, + // since the body of the original message is overwritten by the body + // coming from the stream. + // These are all content-* headers. + + // Default transfer encoding is 7bit if not set + $encoding = ''; + // Remove all existing transfer encoding headers + $messageHeaders->removeAll('Content-Transfer-Encoding'); + // See whether the stream sets the transfer encoding + if (isset($headers['content-transfer-encoding'])) { + $encoding = $headers['content-transfer-encoding']; + } + + // We use the null content encoder, since the body is already encoded + // according to the transfer encoding specified in the stream + $message->setEncoder(new Swift_Mime_ContentEncoder_NullContentEncoder($encoding)); + + // Set the disposition, if present + if (isset($headers['content-disposition'])) { + $messageHeaders->addTextHeader('Content-Disposition', $headers['content-disposition']); + } + + // Copy over the body from the stream using the content type dictated + // by the stream content + $message->setChildren([]); + $message->setBody($messageStream, $headers['content-type']); + } + + /** + * This message will parse the headers of a MIME email byte stream + * and return an array that contains the headers as an associative + * array and the email body as a string. + * + * @return array + */ + protected function parseStream(Swift_OutputByteStream $emailStream) + { + $bufferLength = 78; + $headerData = ''; + $headerBodySeparator = "\r\n\r\n"; + + $emailStream->setReadPointer(0); + + // Read out the headers section from the stream to a string + while (false !== ($buffer = $emailStream->read($bufferLength))) { + $headerData .= $buffer; + + $headersPosEnd = strpos($headerData, $headerBodySeparator); + + // Stop reading if we found the end of the headers + if (false !== $headersPosEnd) { + break; + } + } + + // Split the header data into lines + $headerData = trim(substr($headerData, 0, $headersPosEnd)); + $headerLines = explode("\r\n", $headerData); + unset($headerData); + + $headers = []; + $currentHeaderName = ''; + + // Transform header lines into an associative array + foreach ($headerLines as $headerLine) { + // Handle headers that span multiple lines + if (false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + // Read the entire email body into a byte stream + $bodyStream = new Swift_ByteStream_TemporaryFileByteStream(); + + // Skip the header and separator and point to the body + $emailStream->setReadPointer($headersPosEnd + \strlen($headerBodySeparator)); + + while (false !== ($buffer = $emailStream->read($bufferLength))) { + $bodyStream->write($buffer); + } + + $bodyStream->commit(); + + return [$headers, $bodyStream]; + } + + protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream) + { + $bufferLength = 4096; + $filteredStream = new Swift_ByteStream_TemporaryFileByteStream(); + $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $filteredStream->write($buffer); + } + + $filteredStream->flushBuffers(); + + while (false !== ($buffer = $filteredStream->read($bufferLength))) { + $toStream->write($buffer); + } + + $toStream->commit(); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php new file mode 100644 index 0000000..726b468 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php @@ -0,0 +1,42 @@ +createDependenciesFor('transport.smtp') + ); + + $this->setHost($host); + $this->setPort($port); + $this->setEncryption($encryption); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php new file mode 100644 index 0000000..9d0e8fe --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for spools. + * + * @author Fabien Potencier + */ +interface Swift_Spool +{ + /** + * Starts this Spool mechanism. + */ + public function start(); + + /** + * Stops this Spool mechanism. + */ + public function stop(); + + /** + * Tests if this Spool mechanism has started. + * + * @return bool + */ + public function isStarted(); + + /** + * Queues a message. + * + * @param Swift_Mime_SimpleMessage $message The message to store + * + * @return bool Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_SimpleMessage $message); + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php new file mode 100644 index 0000000..c08e0fb --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @author Fabien Potencier + */ +class Swift_SpoolTransport extends Swift_Transport_SpoolTransport +{ + /** + * Create a new SpoolTransport. + */ + public function __construct(Swift_Spool $spool) + { + $arguments = Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.spool'); + + $arguments[] = $spool; + + \call_user_func_array( + [$this, 'Swift_Transport_SpoolTransport::__construct'], + $arguments + ); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php new file mode 100644 index 0000000..362be2e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php @@ -0,0 +1,35 @@ +index = []; + $this->tree = []; + $this->replace = []; + $this->repSize = []; + + $tree = null; + $i = null; + $last_size = $size = 0; + foreach ($search as $i => $search_element) { + if (null !== $tree) { + $tree[-1] = min(\count($replace) - 1, $i - 1); + $tree[-2] = $last_size; + } + $tree = &$this->tree; + if (\is_array($search_element)) { + foreach ($search_element as $k => $char) { + $this->index[$char] = true; + if (!isset($tree[$char])) { + $tree[$char] = []; + } + $tree = &$tree[$char]; + } + $last_size = $k + 1; + $size = max($size, $last_size); + } else { + $last_size = 1; + if (!isset($tree[$search_element])) { + $tree[$search_element] = []; + } + $tree = &$tree[$search_element]; + $size = max($last_size, $size); + $this->index[$search_element] = true; + } + } + if (null !== $i) { + $tree[-1] = min(\count($replace) - 1, $i); + $tree[-2] = $last_size; + $this->treeMaxLen = $size; + } + foreach ($replace as $rep) { + if (!\is_array($rep)) { + $rep = [$rep]; + } + $this->replace[] = $rep; + } + for ($i = \count($this->replace) - 1; $i >= 0; --$i) { + $this->replace[$i] = $rep = $this->filter($this->replace[$i], $i); + $this->repSize[$i] = \count($rep); + } + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param array $buffer + * + * @return bool + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = end($buffer); + + return isset($this->index[$endOfBuffer]); + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param array $buffer + * @param int $minReplaces + * + * @return array + */ + public function filter($buffer, $minReplaces = -1) + { + if (0 == $this->treeMaxLen) { + return $buffer; + } + + $newBuffer = []; + $buf_size = \count($buffer); + $last_size = 0; + for ($i = 0; $i < $buf_size; ++$i) { + $search_pos = $this->tree; + $last_found = PHP_INT_MAX; + // We try to find if the next byte is part of a search pattern + for ($j = 0; $j <= $this->treeMaxLen; ++$j) { + // We have a new byte for a search pattern + if (isset($buffer[$p = $i + $j]) && isset($search_pos[$buffer[$p]])) { + $search_pos = $search_pos[$buffer[$p]]; + // We have a complete pattern, save, in case we don't find a better match later + if (isset($search_pos[-1]) && $search_pos[-1] < $last_found + && $search_pos[-1] > $minReplaces) { + $last_found = $search_pos[-1]; + $last_size = $search_pos[-2]; + } + } + // We got a complete pattern + elseif (PHP_INT_MAX !== $last_found) { + // Adding replacement datas to output buffer + $rep_size = $this->repSize[$last_found]; + for ($j = 0; $j < $rep_size; ++$j) { + $newBuffer[] = $this->replace[$last_found][$j]; + } + // We Move cursor forward + $i += $last_size - 1; + // Edge Case, last position in buffer + if ($i >= $buf_size) { + $newBuffer[] = $buffer[$i]; + } + + // We start the next loop + continue 2; + } else { + // this byte is not in a pattern and we haven't found another pattern + break; + } + } + // Normal byte, move it to output buffer + $newBuffer[] = $buffer[$i]; + } + + return $newBuffer; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php new file mode 100644 index 0000000..50a63f1 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php @@ -0,0 +1,70 @@ +search = $search; + $this->replace = $replace; + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param string $buffer + * + * @return bool + */ + public function shouldBuffer($buffer) + { + if ('' === $buffer) { + return false; + } + + $endOfBuffer = substr($buffer, -1); + foreach ((array) $this->search as $needle) { + if (false !== strpos($needle, $endOfBuffer)) { + return true; + } + } + + return false; + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param string $buffer + * + * @return string + */ + public function filter($buffer) + { + return str_replace($this->search, $this->replace, $buffer); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php new file mode 100644 index 0000000..783b889 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php @@ -0,0 +1,45 @@ +filters[$search][$replace])) { + if (!isset($this->filters[$search])) { + $this->filters[$search] = []; + } + + if (!isset($this->filters[$search][$replace])) { + $this->filters[$search][$replace] = []; + } + + $this->filters[$search][$replace] = new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + return $this->filters[$search][$replace]; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php new file mode 100644 index 0000000..15e68b1 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php @@ -0,0 +1,28 @@ +ping()) { + * $transport->stop(); + * $transport->start(); + * } + * + * The Transport mechanism will be started, if it is not already. + * + * It is undefined if the Transport mechanism attempts to restart as long as + * the return value reflects whether the mechanism is now functional. + * + * @return bool TRUE if the transport is alive + */ + public function ping(); + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * This is the responsibility of the send method to start the transport if needed. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null); + + /** + * Register a plugin in the Transport. + */ + public function registerPlugin(Swift_Events_EventListener $plugin); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php new file mode 100644 index 0000000..546b2d8 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php @@ -0,0 +1,556 @@ +buffer = $buf; + $this->eventDispatcher = $dispatcher; + $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder(); + $this->setLocalDomain($localDomain); + } + + /** + * Set the name of the local domain which Swift will identify itself as. + * + * This should be a fully-qualified domain name and should be truly the domain + * you're using. + * + * If your server does not have a domain name, use the IP address. This will + * automatically be wrapped in square brackets as described in RFC 5321, + * section 4.1.3. + * + * @param string $domain + * + * @return $this + */ + public function setLocalDomain($domain) + { + if ('[' !== substr($domain, 0, 1)) { + if (filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $domain = '['.$domain.']'; + } elseif (filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $domain = '[IPv6:'.$domain.']'; + } + } + + $this->domain = $domain; + + return $this; + } + + /** + * Get the name of the domain Swift will identify as. + * + * If an IP address was specified, this will be returned wrapped in square + * brackets as described in RFC 5321, section 4.1.3. + * + * @return string + */ + public function getLocalDomain() + { + return $this->domain; + } + + /** + * Sets the source IP. + * + * @param string $source + */ + public function setSourceIp($source) + { + $this->sourceIp = $source; + } + + /** + * Returns the IP used to connect to the destination. + * + * @return string + */ + public function getSourceIp() + { + return $this->sourceIp; + } + + public function setAddressEncoder(Swift_AddressEncoder $addressEncoder) + { + $this->addressEncoder = $addressEncoder; + } + + public function getAddressEncoder() + { + return $this->addressEncoder; + } + + /** + * Start the SMTP connection. + */ + public function start() + { + if (!$this->started) { + if ($evt = $this->eventDispatcher->createTransportChangeEvent($this)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->buffer->initialize($this->getBufferParams()); + } catch (Swift_TransportException $e) { + $this->throwException($e); + } + $this->readGreeting(); + $this->doHeloCommand(); + + if ($evt) { + $this->eventDispatcher->dispatchEvent($evt, 'transportStarted'); + } + + $this->started = true; + } + } + + /** + * Test if an SMTP connection has been established. + * + * @return bool + */ + public function isStarted() + { + return $this->started; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + if (!$this->isStarted()) { + $this->start(); + } + + $sent = 0; + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (!$reversePath = $this->getReversePath($message)) { + $this->throwException(new Swift_TransportException('Cannot send message without a sender address')); + } + + $to = (array) $message->getTo(); + $cc = (array) $message->getCc(); + $tos = array_merge($to, $cc); + $bcc = (array) $message->getBcc(); + + $message->setBcc([]); + + try { + $sent += $this->sendTo($message, $reversePath, $tos, $failedRecipients); + $sent += $this->sendBcc($message, $reversePath, $bcc, $failedRecipients); + } finally { + $message->setBcc($bcc); + } + + if ($evt) { + if ($sent == \count($to) + \count($cc) + \count($bcc)) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + } elseif ($sent > 0) { + $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE); + } else { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + } + $evt->setFailedRecipients($failedRecipients); + $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); //Make sure a new Message ID is used + + return $sent; + } + + /** + * Stop the SMTP connection. + */ + public function stop() + { + if ($this->started) { + if ($evt = $this->eventDispatcher->createTransportChangeEvent($this)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->executeCommand("QUIT\r\n", [221]); + } catch (Swift_TransportException $e) { + } + + try { + $this->buffer->terminate(); + + if ($evt) { + $this->eventDispatcher->dispatchEvent($evt, 'transportStopped'); + } + } catch (Swift_TransportException $e) { + $this->throwException($e); + } + } + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function ping() + { + try { + if (!$this->isStarted()) { + $this->start(); + } + + $this->executeCommand("NOOP\r\n", [250]); + } catch (Swift_TransportException $e) { + try { + $this->stop(); + } catch (Swift_TransportException $e) { + } + + return false; + } + + return true; + } + + /** + * Register a plugin. + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->eventDispatcher->bindEventListener($plugin); + } + + /** + * Reset the current mail transaction. + */ + public function reset() + { + $this->executeCommand("RSET\r\n", [250], $failures, true); + } + + /** + * Get the IoBuffer where read/writes are occurring. + * + * @return Swift_Transport_IoBuffer + */ + public function getBuffer() + { + return $this->buffer; + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * If the command is RCPT TO, and the pipeline is non-empty, no exception + * will be thrown; instead the failing address is added to $failures. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * @param bool $pipeline Do not wait for response + * @param string $address the address, if command is RCPT TO + * + * @return string|null The server response, or null if pipelining is enabled + */ + public function executeCommand($command, $codes = [], &$failures = null, $pipeline = false, $address = null) + { + $failures = (array) $failures; + $seq = $this->buffer->write($command); + if ($evt = $this->eventDispatcher->createCommandEvent($this, $command, $codes)) { + $this->eventDispatcher->dispatchEvent($evt, 'commandSent'); + } + + $this->pipeline[] = [$command, $seq, $codes, $address]; + + if ($pipeline && $this->pipelining) { + return null; + } + + $response = null; + + while ($this->pipeline) { + list($command, $seq, $codes, $address) = array_shift($this->pipeline); + $response = $this->getFullResponse($seq); + try { + $this->assertResponseCode($response, $codes); + } catch (Swift_TransportException $e) { + if ($this->pipeline && $address) { + $failures[] = $address; + } else { + $this->throwException($e); + } + } + } + + return $response; + } + + /** Read the opening SMTP greeting */ + protected function readGreeting() + { + $this->assertResponseCode($this->getFullResponse(0), [220]); + } + + /** Send the HELO welcome */ + protected function doHeloCommand() + { + $this->executeCommand( + sprintf("HELO %s\r\n", $this->domain), [250] + ); + } + + /** Send the MAIL FROM command */ + protected function doMailFromCommand($address) + { + $address = $this->addressEncoder->encodeString($address); + $this->executeCommand( + sprintf("MAIL FROM:<%s>\r\n", $address), [250], $failures, true + ); + } + + /** Send the RCPT TO command */ + protected function doRcptToCommand($address) + { + $address = $this->addressEncoder->encodeString($address); + $this->executeCommand( + sprintf("RCPT TO:<%s>\r\n", $address), [250, 251, 252], $failures, true, $address + ); + } + + /** Send the DATA command */ + protected function doDataCommand(&$failedRecipients) + { + $this->executeCommand("DATA\r\n", [354], $failedRecipients); + } + + /** Stream the contents of the message over the buffer */ + protected function streamMessage(Swift_Mime_SimpleMessage $message) + { + $this->buffer->setWriteTranslations(["\r\n." => "\r\n.."]); + try { + $message->toByteStream($this->buffer); + $this->buffer->flushBuffers(); + } catch (Swift_TransportException $e) { + $this->throwException($e); + } + $this->buffer->setWriteTranslations([]); + $this->executeCommand("\r\n.\r\n", [250]); + } + + /** Determine the best-use reverse path for this message */ + protected function getReversePath(Swift_Mime_SimpleMessage $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) { + $path = $return; + } elseif (!empty($sender)) { + // Don't use array_keys + reset($sender); // Reset Pointer to first pos + $path = key($sender); // Get key + } elseif (!empty($from)) { + reset($from); // Reset Pointer to first pos + $path = key($from); // Get key + } + + return $path; + } + + /** Throw a TransportException, first sending it to any listeners */ + protected function throwException(Swift_TransportException $e) + { + if ($evt = $this->eventDispatcher->createTransportExceptionEvent($this, $e)) { + $this->eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); + if (!$evt->bubbleCancelled()) { + throw $e; + } + } else { + throw $e; + } + } + + /** Throws an Exception if a response code is incorrect */ + protected function assertResponseCode($response, $wanted) + { + if (!$response) { + $this->throwException(new Swift_TransportException('Expected response code '.implode('/', $wanted).' but got an empty response')); + } + + list($code) = sscanf($response, '%3d'); + $valid = (empty($wanted) || \in_array($code, $wanted)); + + if ($evt = $this->eventDispatcher->createResponseEvent($this, $response, + $valid)) { + $this->eventDispatcher->dispatchEvent($evt, 'responseReceived'); + } + + if (!$valid) { + $this->throwException(new Swift_TransportException('Expected response code '.implode('/', $wanted).' but got code "'.$code.'", with message "'.$response.'"', $code)); + } + } + + /** Get an entire multi-line response using its sequence number */ + protected function getFullResponse($seq) + { + $response = ''; + try { + do { + $line = $this->buffer->readLine($seq); + $response .= $line; + } while (null !== $line && false !== $line && ' ' != $line[3]); + } catch (Swift_TransportException $e) { + $this->throwException($e); + } catch (Swift_IoException $e) { + $this->throwException(new Swift_TransportException($e->getMessage(), 0, $e)); + } + + return $response; + } + + /** Send an email to the given recipients from the given reverse path */ + private function doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients) + { + $sent = 0; + $this->doMailFromCommand($reversePath); + foreach ($recipients as $forwardPath) { + try { + $this->doRcptToCommand($forwardPath); + ++$sent; + } catch (Swift_TransportException $e) { + $failedRecipients[] = $forwardPath; + } catch (Swift_AddressEncoderException $e) { + $failedRecipients[] = $forwardPath; + } + } + + if (0 != $sent) { + $sent += \count($failedRecipients); + $this->doDataCommand($failedRecipients); + $sent -= \count($failedRecipients); + + $this->streamMessage($message); + } else { + $this->reset(); + } + + return $sent; + } + + /** Send a message to the given To: recipients */ + private function sendTo(Swift_Mime_SimpleMessage $message, $reversePath, array $to, array &$failedRecipients) + { + if (empty($to)) { + return 0; + } + + return $this->doMailTransaction($message, $reversePath, array_keys($to), + $failedRecipients); + } + + /** Send a message to all Bcc: recipients */ + private function sendBcc(Swift_Mime_SimpleMessage $message, $reversePath, array $bcc, array &$failedRecipients) + { + $sent = 0; + foreach ($bcc as $forwardPath => $name) { + $message->setBcc([$forwardPath => $name]); + $sent += $this->doMailTransaction( + $message, $reversePath, [$forwardPath], $failedRecipients + ); + } + + return $sent; + } + + /** + * Destructor. + */ + public function __destruct() + { + try { + $this->stop(); + } catch (Exception $e) { + } + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php new file mode 100644 index 0000000..e7ccf6d --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php @@ -0,0 +1,75 @@ +executeCommand("AUTH CRAM-MD5\r\n", [334]); + $challenge = base64_decode(substr($challenge, 4)); + $message = base64_encode( + $username.' '.$this->getResponse($password, $challenge) + ); + $agent->executeCommand(sprintf("%s\r\n", $message), [235]); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", [250]); + + throw $e; + } + } + + /** + * Generate a CRAM-MD5 response from a server challenge. + * + * @param string $secret + * @param string $challenge + * + * @return string + */ + private function getResponse($secret, $challenge) + { + if (\strlen($secret) > 64) { + $secret = pack('H32', md5($secret)); + } + + if (\strlen($secret) < 64) { + $secret = str_pad($secret, 64, \chr(0)); + } + + $k_ipad = substr($secret, 0, 64) ^ str_repeat(\chr(0x36), 64); + $k_opad = substr($secret, 0, 64) ^ str_repeat(\chr(0x5C), 64); + + $inner = pack('H32', md5($k_ipad.$challenge)); + $digest = md5($k_opad.$inner); + + return $digest; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php new file mode 100644 index 0000000..458c038 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php @@ -0,0 +1,45 @@ +executeCommand("AUTH LOGIN\r\n", [334]); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), [334]); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), [235]); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", [250]); + + throw $e; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php new file mode 100644 index 0000000..21c070e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php @@ -0,0 +1,681 @@ + + */ +class Swift_Transport_Esmtp_Auth_NTLMAuthenticator implements Swift_Transport_Esmtp_Authenticator +{ + const NTLMSIG = "NTLMSSP\x00"; + const DESCONST = 'KGS!@#$%'; + + /** + * Get the name of the AUTH mechanism this Authenticator handles. + * + * @return string + */ + public function getAuthKeyword() + { + return 'NTLM'; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException + */ + public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password) + { + if (!\function_exists('openssl_encrypt')) { + throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.'); + } + + if (!\function_exists('bcmul')) { + throw new LogicException('The BCMath functions must be enabled to use the NTLM authenticator.'); + } + + try { + // execute AUTH command and filter out the code at the beginning + // AUTH NTLM xxxx + $response = base64_decode(substr(trim($this->sendMessage1($agent)), 4)); + + // extra parameters for our unit cases + $timestamp = \func_num_args() > 3 ? func_get_arg(3) : $this->getCorrectTimestamp(bcmul(microtime(true), '1000')); + $client = \func_num_args() > 4 ? func_get_arg(4) : random_bytes(8); + + // Message 3 response + $this->sendMessage3($response, $username, $password, $timestamp, $client, $agent); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", [250]); + + throw $e; + } + } + + protected function si2bin($si, $bits = 32) + { + $bin = null; + if ($si >= -2 ** ($bits - 1) && ($si <= 2 ** ($bits - 1))) { + // positive or zero + if ($si >= 0) { + $bin = base_convert($si, 10, 2); + // pad to $bits bit + $bin_length = \strlen($bin); + if ($bin_length < $bits) { + $bin = str_repeat('0', $bits - $bin_length).$bin; + } + } else { + // negative + $si = -$si - 2 ** $bits; + $bin = base_convert($si, 10, 2); + $bin_length = \strlen($bin); + if ($bin_length > $bits) { + $bin = str_repeat('1', $bits - $bin_length).$bin; + } + } + } + + return $bin; + } + + /** + * Send our auth message and returns the response. + * + * @return string SMTP Response + */ + protected function sendMessage1(Swift_Transport_SmtpAgent $agent) + { + $message = $this->createMessage1(); + + return $agent->executeCommand(sprintf("AUTH %s %s\r\n", $this->getAuthKeyword(), base64_encode($message)), [334]); + } + + /** + * Fetch all details of our response (message 2). + * + * @param string $response + * + * @return array our response parsed + */ + protected function parseMessage2($response) + { + $responseHex = bin2hex($response); + $length = floor(hexdec(substr($responseHex, 28, 4)) / 256) * 2; + $offset = floor(hexdec(substr($responseHex, 32, 4)) / 256) * 2; + $challenge = hex2bin(substr($responseHex, 48, 16)); + $context = hex2bin(substr($responseHex, 64, 16)); + $targetInfoH = hex2bin(substr($responseHex, 80, 16)); + $targetName = hex2bin(substr($responseHex, $offset, $length)); + $offset = floor(hexdec(substr($responseHex, 88, 4)) / 256) * 2; + $targetInfoBlock = substr($responseHex, $offset); + list($domainName, $serverName, $DNSDomainName, $DNSServerName, $terminatorByte) = $this->readSubBlock($targetInfoBlock); + + return [ + $challenge, + $context, + $targetInfoH, + $targetName, + $domainName, + $serverName, + $DNSDomainName, + $DNSServerName, + hex2bin($targetInfoBlock), + $terminatorByte, + ]; + } + + /** + * Read the blob information in from message2. + * + * @return array + */ + protected function readSubBlock($block) + { + // remove terminatorByte cause it's always the same + $block = substr($block, 0, -8); + + $length = \strlen($block); + $offset = 0; + $data = []; + while ($offset < $length) { + $blockLength = hexdec(substr(substr($block, $offset, 8), -4)) / 256; + $offset += 8; + $data[] = hex2bin(substr($block, $offset, $blockLength * 2)); + $offset += $blockLength * 2; + } + + if (3 == \count($data)) { + $data[] = $data[2]; + $data[2] = ''; + } + + $data[] = $this->createByte('00'); + + return $data; + } + + /** + * Send our final message with all our data. + * + * @param string $response Message 1 response (message 2) + * @param string $username + * @param string $password + * @param string $timestamp + * @param string $client + * @param bool $v2 Use version2 of the protocol + * + * @return string + */ + protected function sendMessage3($response, $username, $password, $timestamp, $client, Swift_Transport_SmtpAgent $agent, $v2 = true) + { + list($domain, $username) = $this->getDomainAndUsername($username); + //$challenge, $context, $targetInfoH, $targetName, $domainName, $workstation, $DNSDomainName, $DNSServerName, $blob, $ter + list($challenge, , , , , $workstation, , , $blob) = $this->parseMessage2($response); + + if (!$v2) { + // LMv1 + $lmResponse = $this->createLMPassword($password, $challenge); + // NTLMv1 + $ntlmResponse = $this->createNTLMPassword($password, $challenge); + } else { + // LMv2 + $lmResponse = $this->createLMv2Password($password, $username, $domain, $challenge, $client); + // NTLMv2 + $ntlmResponse = $this->createNTLMv2Hash($password, $username, $domain, $challenge, $blob, $timestamp, $client); + } + + $message = $this->createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse); + + return $agent->executeCommand(sprintf("%s\r\n", base64_encode($message)), [235]); + } + + /** + * Create our message 1. + * + * @return string + */ + protected function createMessage1() + { + return self::NTLMSIG + .$this->createByte('01') // Message 1 +.$this->createByte('0702'); // Flags + } + + /** + * Create our message 3. + * + * @param string $domain + * @param string $username + * @param string $workstation + * @param string $lmResponse + * @param string $ntlmResponse + * + * @return string + */ + protected function createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse) + { + // Create security buffers + $domainSec = $this->createSecurityBuffer($domain, 64); + $domainInfo = $this->readSecurityBuffer(bin2hex($domainSec)); + $userSec = $this->createSecurityBuffer($username, ($domainInfo[0] + $domainInfo[1]) / 2); + $userInfo = $this->readSecurityBuffer(bin2hex($userSec)); + $workSec = $this->createSecurityBuffer($workstation, ($userInfo[0] + $userInfo[1]) / 2); + $workInfo = $this->readSecurityBuffer(bin2hex($workSec)); + $lmSec = $this->createSecurityBuffer($lmResponse, ($workInfo[0] + $workInfo[1]) / 2, true); + $lmInfo = $this->readSecurityBuffer(bin2hex($lmSec)); + $ntlmSec = $this->createSecurityBuffer($ntlmResponse, ($lmInfo[0] + $lmInfo[1]) / 2, true); + + return self::NTLMSIG + .$this->createByte('03') // TYPE 3 message +.$lmSec // LM response header +.$ntlmSec // NTLM response header +.$domainSec // Domain header +.$userSec // User header +.$workSec // Workstation header +.$this->createByte('000000009a', 8) // session key header (empty) +.$this->createByte('01020000') // FLAGS +.$this->convertTo16bit($domain) // domain name +.$this->convertTo16bit($username) // username +.$this->convertTo16bit($workstation) // workstation +.$lmResponse + .$ntlmResponse; + } + + /** + * @param string $timestamp Epoch timestamp in microseconds + * @param string $client Random bytes + * @param string $targetInfo + * + * @return string + */ + protected function createBlob($timestamp, $client, $targetInfo) + { + return $this->createByte('0101') + .$this->createByte('00') + .$timestamp + .$client + .$this->createByte('00') + .$targetInfo + .$this->createByte('00'); + } + + /** + * Get domain and username from our username. + * + * @example DOMAIN\username + * + * @param string $name + * + * @return array + */ + protected function getDomainAndUsername($name) + { + if (false !== strpos($name, '\\')) { + return explode('\\', $name); + } + + if (false !== strpos($name, '@')) { + list($user, $domain) = explode('@', $name); + + return [$domain, $user]; + } + + // no domain passed + return ['', $name]; + } + + /** + * Create LMv1 response. + * + * @param string $password + * @param string $challenge + * + * @return string + */ + protected function createLMPassword($password, $challenge) + { + // FIRST PART + $password = $this->createByte(strtoupper($password), 14, false); + list($key1, $key2) = str_split($password, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + + $constantDecrypt = $this->createByte($this->desEncrypt(self::DESCONST, $desKey1).$this->desEncrypt(self::DESCONST, $desKey2), 21, false); + + // SECOND PART + list($key1, $key2, $key3) = str_split($constantDecrypt, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + $desKey3 = $this->createDesKey($key3); + + return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3); + } + + /** + * Create NTLMv1 response. + * + * @param string $password + * @param string $challenge + * + * @return string + */ + protected function createNTLMPassword($password, $challenge) + { + // FIRST PART + $ntlmHash = $this->createByte($this->md4Encrypt($password), 21, false); + list($key1, $key2, $key3) = str_split($ntlmHash, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + $desKey3 = $this->createDesKey($key3); + + return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3); + } + + /** + * Convert a normal timestamp to a tenth of a microtime epoch time. + * + * @param string $time + * + * @return string + */ + protected function getCorrectTimestamp($time) + { + // Get our timestamp (tricky!) + $time = number_format($time, 0, '.', ''); // save microtime to string + $time = bcadd($time, '11644473600000', 0); // add epoch time + $time = bcmul($time, 10000, 0); // tenths of a microsecond. + + $binary = $this->si2bin($time, 64); // create 64 bit binary string + $timestamp = ''; + for ($i = 0; $i < 8; ++$i) { + $timestamp .= \chr(bindec(substr($binary, -(($i + 1) * 8), 8))); + } + + return $timestamp; + } + + /** + * Create LMv2 response. + * + * @param string $password + * @param string $username + * @param string $domain + * @param string $challenge NTLM Challenge + * @param string $client Random string + * + * @return string + */ + protected function createLMv2Password($password, $username, $domain, $challenge, $client) + { + $lmPass = '00'; // by default 00 + // if $password > 15 than we can't use this method + if (\strlen($password) <= 15) { + $ntlmHash = $this->md4Encrypt($password); + $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain)); + + $lmPass = bin2hex($this->md5Encrypt($ntml2Hash, $challenge.$client).$client); + } + + return $this->createByte($lmPass, 24); + } + + /** + * Create NTLMv2 response. + * + * @param string $password + * @param string $username + * @param string $domain + * @param string $challenge Hex values + * @param string $targetInfo Hex values + * @param string $timestamp + * @param string $client Random bytes + * + * @return string + * + * @see http://davenport.sourceforge.net/ntlm.html#theNtlmResponse + */ + protected function createNTLMv2Hash($password, $username, $domain, $challenge, $targetInfo, $timestamp, $client) + { + $ntlmHash = $this->md4Encrypt($password); + $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain)); + + // create blob + $blob = $this->createBlob($timestamp, $client, $targetInfo); + + $ntlmv2Response = $this->md5Encrypt($ntml2Hash, $challenge.$blob); + + return $ntlmv2Response.$blob; + } + + protected function createDesKey($key) + { + $material = [bin2hex($key[0])]; + $len = \strlen($key); + for ($i = 1; $i < $len; ++$i) { + list($high, $low) = str_split(bin2hex($key[$i])); + $v = $this->castToByte(\ord($key[$i - 1]) << (7 + 1 - $i) | $this->uRShift(hexdec(dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xf)), $i)); + $material[] = str_pad(substr(dechex($v), -2), 2, '0', STR_PAD_LEFT); // cast to byte + } + $material[] = str_pad(substr(dechex($this->castToByte(\ord($key[6]) << 1)), -2), 2, '0'); + + // odd parity + foreach ($material as $k => $v) { + $b = $this->castToByte(hexdec($v)); + $needsParity = 0 == (($this->uRShift($b, 7) ^ $this->uRShift($b, 6) ^ $this->uRShift($b, 5) + ^ $this->uRShift($b, 4) ^ $this->uRShift($b, 3) ^ $this->uRShift($b, 2) + ^ $this->uRShift($b, 1)) & 0x01); + + list($high, $low) = str_split($v); + if ($needsParity) { + $material[$k] = dechex(hexdec($high) | 0x0).dechex(hexdec($low) | 0x1); + } else { + $material[$k] = dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xe); + } + } + + return hex2bin(implode('', $material)); + } + + /** HELPER FUNCTIONS */ + + /** + * Create our security buffer depending on length and offset. + * + * @param string $value Value we want to put in + * @param int $offset start of value + * @param bool $is16 Do we 16bit string or not? + * + * @return string + */ + protected function createSecurityBuffer($value, $offset, $is16 = false) + { + $length = \strlen(bin2hex($value)); + $length = $is16 ? $length / 2 : $length; + $length = $this->createByte(str_pad(dechex($length), 2, '0', STR_PAD_LEFT), 2); + + return $length.$length.$this->createByte(dechex($offset), 4); + } + + /** + * Read our security buffer to fetch length and offset of our value. + * + * @param string $value Securitybuffer in hex + * + * @return array array with length and offset + */ + protected function readSecurityBuffer($value) + { + $length = floor(hexdec(substr($value, 0, 4)) / 256) * 2; + $offset = floor(hexdec(substr($value, 8, 4)) / 256) * 2; + + return [$length, $offset]; + } + + /** + * Cast to byte java equivalent to (byte). + * + * @param int $v + * + * @return int + */ + protected function castToByte($v) + { + return (($v + 128) % 256) - 128; + } + + /** + * Java unsigned right bitwise + * $a >>> $b. + * + * @param int $a + * @param int $b + * + * @return int + */ + protected function uRShift($a, $b) + { + if (0 == $b) { + return $a; + } + + return ($a >> $b) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($b - 1)); + } + + /** + * Right padding with 0 to certain length. + * + * @param string $input + * @param int $bytes Length of bytes + * @param bool $isHex Did we provided hex value + * + * @return string + */ + protected function createByte($input, $bytes = 4, $isHex = true) + { + if ($isHex) { + $byte = hex2bin(str_pad($input, $bytes * 2, '00')); + } else { + $byte = str_pad($input, $bytes, "\x00"); + } + + return $byte; + } + + /** ENCRYPTION ALGORITHMS */ + + /** + * DES Encryption. + * + * @param string $value An 8-byte string + * @param string $key + * + * @return string + */ + protected function desEncrypt($value, $key) + { + return substr(openssl_encrypt($value, 'DES-ECB', $key, \OPENSSL_RAW_DATA), 0, 8); + } + + /** + * MD5 Encryption. + * + * @param string $key Encryption key + * @param string $msg Message to encrypt + * + * @return string + */ + protected function md5Encrypt($key, $msg) + { + $blocksize = 64; + if (\strlen($key) > $blocksize) { + $key = pack('H*', md5($key)); + } + + $key = str_pad($key, $blocksize, "\0"); + $ipadk = $key ^ str_repeat("\x36", $blocksize); + $opadk = $key ^ str_repeat("\x5c", $blocksize); + + return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg)))); + } + + /** + * MD4 Encryption. + * + * @param string $input + * + * @return string + * + * @see https://secure.php.net/manual/en/ref.hash.php + */ + protected function md4Encrypt($input) + { + $input = $this->convertTo16bit($input); + + return \function_exists('hash') ? hex2bin(hash('md4', $input)) : mhash(MHASH_MD4, $input); + } + + /** + * Convert UTF-8 to UTF-16. + * + * @param string $input + * + * @return string + */ + protected function convertTo16bit($input) + { + return iconv('UTF-8', 'UTF-16LE', $input); + } + + /** + * @param string $message + */ + protected function debug($message) + { + $message = bin2hex($message); + $messageId = substr($message, 16, 8); + echo substr($message, 0, 16)." NTLMSSP Signature
    \n"; + echo $messageId." Type Indicator
    \n"; + + if ('02000000' == $messageId) { + $map = [ + 'Challenge', + 'Context', + 'Target Information Security Buffer', + 'Target Name Data', + 'NetBIOS Domain Name', + 'NetBIOS Server Name', + 'DNS Domain Name', + 'DNS Server Name', + 'BLOB', + 'Target Information Terminator', + ]; + + $data = $this->parseMessage2(hex2bin($message)); + + foreach ($map as $key => $value) { + echo bin2hex($data[$key]).' - '.$data[$key].' ||| '.$value."
    \n"; + } + } elseif ('03000000' == $messageId) { + $i = 0; + $data[$i++] = substr($message, 24, 16); + list($lmLength, $lmOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 40, 16); + list($ntmlLength, $ntmlOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 56, 16); + list($targetLength, $targetOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 72, 16); + list($userLength, $userOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 88, 16); + list($workLength, $workOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 104, 16); + $data[$i++] = substr($message, 120, 8); + $data[$i++] = substr($message, $targetOffset, $targetLength); + $data[$i++] = substr($message, $userOffset, $userLength); + $data[$i++] = substr($message, $workOffset, $workLength); + $data[$i++] = substr($message, $lmOffset, $lmLength); + $data[$i] = substr($message, $ntmlOffset, $ntmlLength); + + $map = [ + 'LM Response Security Buffer', + 'NTLM Response Security Buffer', + 'Target Name Security Buffer', + 'User Name Security Buffer', + 'Workstation Name Security Buffer', + 'Session Key Security Buffer', + 'Flags', + 'Target Name Data', + 'User Name Data', + 'Workstation Name Data', + 'LM Response Data', + 'NTLM Response Data', + ]; + + foreach ($map as $key => $value) { + echo $data[$key].' - '.hex2bin($data[$key]).' ||| '.$value."
    \n"; + } + } + + echo '

    '; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php new file mode 100644 index 0000000..41d0a50 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php @@ -0,0 +1,44 @@ +executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), [235]); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", [250]); + + throw $e; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php new file mode 100644 index 0000000..859f22f --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php @@ -0,0 +1,64 @@ + + * $transport = (new Swift_SmtpTransport('smtp.gmail.com', 587, 'tls')) + * ->setAuthMode('XOAUTH2') + * ->setUsername('YOUR_EMAIL_ADDRESS') + * ->setPassword('YOUR_ACCESS_TOKEN'); + * + * + * @author xu.li + * + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol + */ +class Swift_Transport_Esmtp_Auth_XOAuth2Authenticator implements Swift_Transport_Esmtp_Authenticator +{ + /** + * Get the name of the AUTH mechanism this Authenticator handles. + * + * @return string + */ + public function getAuthKeyword() + { + return 'XOAUTH2'; + } + + /** + * {@inheritdoc} + */ + public function authenticate(Swift_Transport_SmtpAgent $agent, $email, $token) + { + try { + $param = $this->constructXOAuth2Params($email, $token); + $agent->executeCommand('AUTH XOAUTH2 '.$param."\r\n", [235]); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", [250]); + + throw $e; + } + } + + /** + * Construct the auth parameter. + * + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism + */ + protected function constructXOAuth2Params($email, $token) + { + return base64_encode("user=$email\1auth=Bearer $token\1\1"); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php new file mode 100644 index 0000000..dd55913 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php @@ -0,0 +1,268 @@ +setAuthenticators($authenticators); + } + + /** + * Set the Authenticators which can process a login request. + * + * @param Swift_Transport_Esmtp_Authenticator[] $authenticators + */ + public function setAuthenticators(array $authenticators) + { + $this->authenticators = $authenticators; + } + + /** + * Get the Authenticators which can process a login request. + * + * @return Swift_Transport_Esmtp_Authenticator[] + */ + public function getAuthenticators() + { + return $this->authenticators; + } + + /** + * Set the username to authenticate with. + * + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Get the username to authenticate with. + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set the password to authenticate with. + * + * @param string $password + */ + public function setPassword($password) + { + $this->password = $password; + } + + /** + * Get the password to authenticate with. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the auth mode to use to authenticate. + * + * @param string $mode + */ + public function setAuthMode($mode) + { + $this->auth_mode = $mode; + } + + /** + * Get the auth mode to use to authenticate. + * + * @return string + */ + public function getAuthMode() + { + return $this->auth_mode; + } + + /** + * Get the name of the ESMTP extension this handles. + * + * @return string + */ + public function getHandledKeyword() + { + return 'AUTH'; + } + + /** + * Set the parameters which the EHLO greeting indicated. + * + * @param string[] $parameters + */ + public function setKeywordParams(array $parameters) + { + $this->esmtpParams = $parameters; + } + + /** + * Runs immediately after a EHLO has been issued. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + */ + public function afterEhlo(Swift_Transport_SmtpAgent $agent) + { + if ($this->username) { + $count = 0; + $errors = []; + foreach ($this->getAuthenticatorsForAgent() as $authenticator) { + if (\in_array(strtolower($authenticator->getAuthKeyword()), array_map('strtolower', $this->esmtpParams))) { + ++$count; + try { + if ($authenticator->authenticate($agent, $this->username, $this->password)) { + return; + } + } catch (Swift_TransportException $e) { + // keep the error message, but tries the other authenticators + $errors[] = [$authenticator->getAuthKeyword(), $e->getMessage()]; + } + } + } + + $message = 'Failed to authenticate on SMTP server with username "'.$this->username.'" using '.$count.' possible authenticators.'; + foreach ($errors as $error) { + $message .= ' Authenticator '.$error[0].' returned '.$error[1].'.'; + } + throw new Swift_TransportException($message); + } + } + + /** + * Not used. + */ + public function getMailParams() + { + return []; + } + + /** + * Not used. + */ + public function getRcptParams() + { + return []; + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods() + { + return ['setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode']; + } + + /** + * Not used. + */ + public function resetState() + { + } + + /** + * Returns the authenticator list for the given agent. + * + * @return array + */ + protected function getAuthenticatorsForAgent() + { + if (!$mode = strtolower($this->auth_mode)) { + return $this->authenticators; + } + + foreach ($this->authenticators as $authenticator) { + if (strtolower($authenticator->getAuthKeyword()) == $mode) { + return [$authenticator]; + } + } + + throw new Swift_TransportException('Auth mode '.$mode.' is invalid'); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php new file mode 100644 index 0000000..f692a6f --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php @@ -0,0 +1,36 @@ +encoding = $encoding; + } + + /** + * Get the name of the ESMTP extension this handles. + * + * @return string + */ + public function getHandledKeyword() + { + return '8BITMIME'; + } + + /** + * Not used. + */ + public function setKeywordParams(array $parameters) + { + } + + /** + * Not used. + */ + public function afterEhlo(Swift_Transport_SmtpAgent $agent) + { + } + + /** + * Get params which are appended to MAIL FROM:<>. + * + * @return string[] + */ + public function getMailParams() + { + return ['BODY='.$this->encoding]; + } + + /** + * Not used. + */ + public function getRcptParams() + { + return []; + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Not used. + */ + public function exposeMixinMethods() + { + return []; + } + + /** + * Not used. + */ + public function resetState() + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/SmtpUtf8Handler.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/SmtpUtf8Handler.php new file mode 100644 index 0000000..7d0252a --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/SmtpUtf8Handler.php @@ -0,0 +1,107 @@ +. + * + * @return string[] + */ + public function getMailParams() + { + return ['SMTPUTF8']; + } + + /** + * Not used. + */ + public function getRcptParams() + { + return []; + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Not used. + */ + public function exposeMixinMethods() + { + return []; + } + + /** + * Not used. + */ + public function resetState() + { + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php new file mode 100644 index 0000000..b8ea36e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php @@ -0,0 +1,86 @@ +. + * + * @return string[] + */ + public function getMailParams(); + + /** + * Get params which are appended to RCPT TO:<>. + * + * @return string[] + */ + public function getRcptParams(); + + /** + * Runs when a command is due to be sent. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + * @param string $command to send + * @param int[] $codes expected in response + * @param string[] $failedRecipients to collect failures + * @param bool $stop to be set true by-reference if the command is now sent + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false); + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword); + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods(); + + /** + * Tells this handler to clear any buffers and reset its state. + */ + public function resetState(); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php new file mode 100644 index 0000000..bce0cdd --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php @@ -0,0 +1,446 @@ + 'tcp', + 'host' => 'localhost', + 'port' => 25, + 'timeout' => 30, + 'blocking' => 1, + 'tls' => false, + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'stream_context_options' => [], + ]; + + /** + * Creates a new EsmtpTransport using the given I/O buffer. + * + * @param Swift_Transport_EsmtpHandler[] $extensionHandlers + * @param string $localDomain + */ + public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null) + { + parent::__construct($buf, $dispatcher, $localDomain, $addressEncoder); + $this->setExtensionHandlers($extensionHandlers); + } + + /** + * Set the host to connect to. + * + * Literal IPv6 addresses should be wrapped in square brackets. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->params['host'] = $host; + + return $this; + } + + /** + * Get the host to connect to. + * + * @return string + */ + public function getHost() + { + return $this->params['host']; + } + + /** + * Set the port to connect to. + * + * @param int $port + * + * @return $this + */ + public function setPort($port) + { + $this->params['port'] = (int) $port; + + return $this; + } + + /** + * Get the port to connect to. + * + * @return int + */ + public function getPort() + { + return $this->params['port']; + } + + /** + * Set the connection timeout. + * + * @param int $timeout seconds + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->params['timeout'] = (int) $timeout; + $this->buffer->setParam('timeout', (int) $timeout); + + return $this; + } + + /** + * Get the connection timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->params['timeout']; + } + + /** + * Set the encryption type (tls or ssl). + * + * @param string $encryption + * + * @return $this + */ + public function setEncryption($encryption) + { + $encryption = strtolower($encryption); + if ('tls' == $encryption) { + $this->params['protocol'] = 'tcp'; + $this->params['tls'] = true; + } else { + $this->params['protocol'] = $encryption; + $this->params['tls'] = false; + } + + return $this; + } + + /** + * Get the encryption type. + * + * @return string + */ + public function getEncryption() + { + return $this->params['tls'] ? 'tls' : $this->params['protocol']; + } + + /** + * Sets the stream context options. + * + * @param array $options + * + * @return $this + */ + public function setStreamOptions($options) + { + $this->params['stream_context_options'] = $options; + + return $this; + } + + /** + * Returns the stream context options. + * + * @return array + */ + public function getStreamOptions() + { + return $this->params['stream_context_options']; + } + + /** + * Sets the source IP. + * + * IPv6 addresses should be wrapped in square brackets. + * + * @param string $source + * + * @return $this + */ + public function setSourceIp($source) + { + $this->params['sourceIp'] = $source; + + return $this; + } + + /** + * Returns the IP used to connect to the destination. + * + * @return string + */ + public function getSourceIp() + { + return $this->params['sourceIp'] ?? null; + } + + /** + * Sets whether SMTP pipelining is enabled. + * + * By default, support is auto-detected using the PIPELINING SMTP extension. + * Use this function to override that in the unlikely event of compatibility + * issues. + * + * @param bool $enabled + * + * @return $this + */ + public function setPipelining($enabled) + { + $this->pipelining = $enabled; + + return $this; + } + + /** + * Returns whether SMTP pipelining is enabled. + * + * @return bool|null a boolean if pipelining is explicitly enabled or disabled, + * or null if support is auto-detected + */ + public function getPipelining() + { + return $this->pipelining; + } + + /** + * Set ESMTP extension handlers. + * + * @param Swift_Transport_EsmtpHandler[] $handlers + * + * @return $this + */ + public function setExtensionHandlers(array $handlers) + { + $assoc = []; + foreach ($handlers as $handler) { + $assoc[$handler->getHandledKeyword()] = $handler; + } + uasort($assoc, function ($a, $b) { + return $a->getPriorityOver($b->getHandledKeyword()); + }); + $this->handlers = $assoc; + $this->setHandlerParams(); + + return $this; + } + + /** + * Get ESMTP extension handlers. + * + * @return Swift_Transport_EsmtpHandler[] + */ + public function getExtensionHandlers() + { + return array_values($this->handlers); + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * @param bool $pipeline Do not wait for response + * @param string $address the address, if command is RCPT TO + * + * @return string|null The server response, or null if pipelining is enabled + */ + public function executeCommand($command, $codes = [], &$failures = null, $pipeline = false, $address = null) + { + $failures = (array) $failures; + $stopSignal = false; + $response = null; + foreach ($this->getActiveHandlers() as $handler) { + $response = $handler->onCommand( + $this, $command, $codes, $failures, $stopSignal + ); + if ($stopSignal) { + return $response; + } + } + + return parent::executeCommand($command, $codes, $failures, $pipeline, $address); + } + + /** Mixin handling method for ESMTP handlers */ + public function __call($method, $args) + { + foreach ($this->handlers as $handler) { + if (\in_array(strtolower($method), + array_map('strtolower', (array) $handler->exposeMixinMethods()) + )) { + $return = \call_user_func_array([$handler, $method], $args); + // Allow fluid method calls + if (null === $return && 'set' == substr($method, 0, 3)) { + return $this; + } else { + return $return; + } + } + } + trigger_error('Call to undefined method '.$method, E_USER_ERROR); + } + + /** Get the params to initialize the buffer */ + protected function getBufferParams() + { + return $this->params; + } + + /** Overridden to perform EHLO instead */ + protected function doHeloCommand() + { + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->domain), [250] + ); + } catch (Swift_TransportException $e) { + return parent::doHeloCommand(); + } + + if ($this->params['tls']) { + try { + $this->executeCommand("STARTTLS\r\n", [220]); + + if (!$this->buffer->startTLS()) { + throw new Swift_TransportException('Unable to connect with TLS encryption'); + } + + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->domain), [250] + ); + } catch (Swift_TransportException $e) { + return parent::doHeloCommand(); + } + } catch (Swift_TransportException $e) { + $this->throwException($e); + } + } + + $this->capabilities = $this->getCapabilities($response); + if (!isset($this->pipelining)) { + $this->pipelining = isset($this->capabilities['PIPELINING']); + } + + $this->setHandlerParams(); + foreach ($this->getActiveHandlers() as $handler) { + $handler->afterEhlo($this); + } + } + + /** Overridden to add Extension support */ + protected function doMailFromCommand($address) + { + $address = $this->addressEncoder->encodeString($address); + $handlers = $this->getActiveHandlers(); + $params = []; + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getMailParams()); + } + $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; + $this->executeCommand( + sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), [250], $failures, true + ); + } + + /** Overridden to add Extension support */ + protected function doRcptToCommand($address) + { + $address = $this->addressEncoder->encodeString($address); + $handlers = $this->getActiveHandlers(); + $params = []; + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getRcptParams()); + } + $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; + $this->executeCommand( + sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), [250, 251, 252], $failures, true, $address + ); + } + + /** Determine ESMTP capabilities by function group */ + private function getCapabilities($ehloResponse) + { + $capabilities = []; + $ehloResponse = trim($ehloResponse); + $lines = explode("\r\n", $ehloResponse); + array_shift($lines); + foreach ($lines as $line) { + if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { + $keyword = strtoupper($matches[1]); + $paramStr = strtoupper(ltrim($matches[2], ' =')); + $params = !empty($paramStr) ? explode(' ', $paramStr) : []; + $capabilities[$keyword] = $params; + } + } + + return $capabilities; + } + + /** Set parameters which are used by each extension handler */ + private function setHandlerParams() + { + foreach ($this->handlers as $keyword => $handler) { + if (\array_key_exists($keyword, $this->capabilities)) { + $handler->setKeywordParams($this->capabilities[$keyword]); + } + } + } + + /** Get ESMTP handlers which are currently ok to use */ + private function getActiveHandlers() + { + $handlers = []; + foreach ($this->handlers as $keyword => $handler) { + if (\array_key_exists($keyword, $this->capabilities)) { + $handlers[] = $handler; + } + } + + return $handlers; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php new file mode 100644 index 0000000..1a4b475 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php @@ -0,0 +1,103 @@ +transports); + for ($i = 0; $i < $maxTransports + && $transport = $this->getNextTransport(); ++$i) { + if ($transport->ping()) { + return true; + } else { + $this->killCurrentTransport(); + } + } + + return \count($this->transports) > 0; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + $maxTransports = \count($this->transports); + $sent = 0; + $this->lastUsedTransport = null; + + for ($i = 0; $i < $maxTransports + && $transport = $this->getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + + if ($sent = $transport->send($message, $failedRecipients)) { + $this->lastUsedTransport = $transport; + + return $sent; + } + } catch (Swift_TransportException $e) { + $this->killCurrentTransport(); + } + } + + if (0 == \count($this->transports)) { + throw new Swift_TransportException('All Transports in FailoverTransport failed, or no Transports available'); + } + + return $sent; + } + + protected function getNextTransport() + { + if (!isset($this->currentTransport)) { + $this->currentTransport = parent::getNextTransport(); + } + + return $this->currentTransport; + } + + protected function killCurrentTransport() + { + $this->currentTransport = null; + parent::killCurrentTransport(); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php new file mode 100644 index 0000000..50f1e5e --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php @@ -0,0 +1,65 @@ +transports = $transports; + $this->deadTransports = []; + } + + /** + * Get $transports to delegate to. + * + * @return Swift_Transport[] + */ + public function getTransports() + { + return array_merge($this->transports, $this->deadTransports); + } + + /** + * Get the Transport used in the last successful send operation. + * + * @return Swift_Transport + */ + public function getLastUsedTransport() + { + return $this->lastUsedTransport; + } + + /** + * Test if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return \count($this->transports) > 0; + } + + /** + * Start this Transport mechanism. + */ + public function start() + { + $this->transports = array_merge($this->transports, $this->deadTransports); + } + + /** + * Stop this Transport mechanism. + */ + public function stop() + { + foreach ($this->transports as $transport) { + $transport->stop(); + } + } + + /** + * {@inheritdoc} + */ + public function ping() + { + foreach ($this->transports as $transport) { + if (!$transport->ping()) { + $this->killCurrentTransport(); + } + } + + return \count($this->transports) > 0; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + $maxTransports = \count($this->transports); + $sent = 0; + $this->lastUsedTransport = null; + + for ($i = 0; $i < $maxTransports + && $transport = $this->getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + if ($sent = $transport->send($message, $failedRecipients)) { + $this->lastUsedTransport = $transport; + break; + } + } catch (Swift_TransportException $e) { + $this->killCurrentTransport(); + } + } + + if (0 == \count($this->transports)) { + throw new Swift_TransportException('All Transports in LoadBalancedTransport failed, or no Transports available'); + } + + return $sent; + } + + /** + * Register a plugin. + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + foreach ($this->transports as $transport) { + $transport->registerPlugin($plugin); + } + } + + /** + * Rotates the transport list around and returns the first instance. + * + * @return Swift_Transport + */ + protected function getNextTransport() + { + if ($next = array_shift($this->transports)) { + $this->transports[] = $next; + } + + return $next; + } + + /** + * Tag the currently used (top of stack) transport as dead/useless. + */ + protected function killCurrentTransport() + { + if ($transport = array_pop($this->transports)) { + try { + $transport->stop(); + } catch (Exception $e) { + } + $this->deadTransports[] = $transport; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php new file mode 100644 index 0000000..7d910db --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @author Fabien Potencier + */ +class Swift_Transport_NullTransport implements Swift_Transport +{ + /** The event dispatcher from the plugin API */ + private $eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher) + { + $this->eventDispatcher = $eventDispatcher; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * {@inheritdoc} + */ + public function ping() + { + return true; + } + + /** + * Sends the given message. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $count = ( + \count((array) $message->getTo()) + + \count((array) $message->getCc()) + + \count((array) $message->getBcc()) + ); + + return $count; + } + + /** + * Register a plugin. + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->eventDispatcher->bindEventListener($plugin); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php new file mode 100644 index 0000000..72ddae8 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php @@ -0,0 +1,158 @@ + 30, + 'blocking' => 1, + 'command' => '/usr/sbin/sendmail -bs', + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS, + ]; + + /** + * Create a new SendmailTransport with $buf for I/O. + * + * @param string $localDomain + */ + public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null) + { + parent::__construct($buf, $dispatcher, $localDomain, $addressEncoder); + } + + /** + * Start the standalone SMTP session if running in -bs mode. + */ + public function start() + { + if (false !== strpos($this->getCommand(), ' -bs')) { + parent::start(); + } + } + + /** + * Set the command to invoke. + * + * If using -t mode you are strongly advised to include -oi or -i in the flags. + * For example: /usr/sbin/sendmail -oi -t + * Swift will append a -f flag if one is not present. + * + * The recommended mode is "-bs" since it is interactive and failure notifications + * are hence possible. + * + * @param string $command + * + * @return $this + */ + public function setCommand($command) + { + $this->params['command'] = $command; + + return $this; + } + + /** + * Get the sendmail command which will be invoked. + * + * @return string + */ + public function getCommand() + { + return $this->params['command']; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * + * The return value is the number of recipients who were accepted for delivery. + * NOTE: If using 'sendmail -t' you will not be aware of any failures until + * they bounce (i.e. send() will always return 100% success). + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + $command = $this->getCommand(); + $buffer = $this->getBuffer(); + $count = 0; + + if (false !== strpos($command, ' -t')) { + if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (false === strpos($command, ' -f')) { + $command .= ' -f'.escapeshellarg($this->getReversePath($message)); + } + + $buffer->initialize(array_merge($this->params, ['command' => $command])); + + if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) { + $buffer->setWriteTranslations(["\r\n" => "\n", "\n." => "\n.."]); + } else { + $buffer->setWriteTranslations(["\r\n" => "\n"]); + } + + $count = \count((array) $message->getTo()) + + \count((array) $message->getCc()) + + \count((array) $message->getBcc()) + ; + $message->toByteStream($buffer); + $buffer->flushBuffers(); + $buffer->setWriteTranslations([]); + $buffer->terminate(); + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + } elseif (false !== strpos($command, ' -bs')) { + $count = parent::send($message, $failedRecipients); + } else { + $this->throwException(new Swift_TransportException( + 'Unsupported sendmail command flags ['.$command.']. '. + 'Must be one of "-bs" or "-t" but can include additional flags.' + )); + } + + return $count; + } + + /** Get the params to initialize the buffer */ + protected function getBufferParams() + { + return $this->params; + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php new file mode 100644 index 0000000..e8ce65c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @author Fabien Potencier + */ +class Swift_Transport_SpoolTransport implements Swift_Transport +{ + /** The spool instance */ + private $spool; + + /** The event dispatcher from the plugin API */ + private $eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null) + { + $this->eventDispatcher = $eventDispatcher; + $this->spool = $spool; + } + + /** + * Sets the spool object. + * + * @return $this + */ + public function setSpool(Swift_Spool $spool) + { + $this->spool = $spool; + + return $this; + } + + /** + * Get the spool object. + * + * @return Swift_Spool + */ + public function getSpool() + { + return $this->spool; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * {@inheritdoc} + */ + public function ping() + { + return true; + } + + /** + * Sends the given message. + * + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent e-mail's + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) { + $this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + $success = $this->spool->queueMessage($message); + + if ($evt) { + $evt->setResult($success ? Swift_Events_SendEvent::RESULT_SPOOLED : Swift_Events_SendEvent::RESULT_FAILED); + $this->eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + return 1; + } + + /** + * Register a plugin. + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->eventDispatcher->bindEventListener($plugin); + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php new file mode 100644 index 0000000..70782ad --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php @@ -0,0 +1,319 @@ +replacementFactory = $replacementFactory; + } + + /** + * Perform any initialization needed, using the given $params. + * + * Parameters will vary depending upon the type of IoBuffer used. + */ + public function initialize(array $params) + { + $this->params = $params; + switch ($params['type']) { + case self::TYPE_PROCESS: + $this->establishProcessConnection(); + break; + case self::TYPE_SOCKET: + default: + $this->establishSocketConnection(); + break; + } + } + + /** + * Set an individual param on the buffer (e.g. switching to SSL). + * + * @param string $param + * @param mixed $value + */ + public function setParam($param, $value) + { + if (isset($this->stream)) { + switch ($param) { + case 'timeout': + if ($this->stream) { + stream_set_timeout($this->stream, $value); + } + break; + + case 'blocking': + if ($this->stream) { + stream_set_blocking($this->stream, 1); + } + } + } + $this->params[$param] = $value; + } + + public function startTLS() + { + // STREAM_CRYPTO_METHOD_TLS_CLIENT only allow tls1.0 connections (some php versions) + // To support modern tls we allow explicit tls1.0, tls1.1, tls1.2 + // Ssl3 and older are not allowed because they are vulnerable + // @TODO make tls arguments configurable + return stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT); + } + + /** + * Perform any shutdown logic needed. + */ + public function terminate() + { + if (isset($this->stream)) { + switch ($this->params['type']) { + case self::TYPE_PROCESS: + fclose($this->in); + fclose($this->out); + proc_close($this->stream); + break; + case self::TYPE_SOCKET: + default: + fclose($this->stream); + break; + } + } + $this->stream = null; + $this->out = null; + $this->in = null; + } + + /** + * Set an array of string replacements which should be made on data written + * to the buffer. + * + * This could replace LF with CRLF for example. + * + * @param string[] $replacements + */ + public function setWriteTranslations(array $replacements) + { + foreach ($this->translations as $search => $replace) { + if (!isset($replacements[$search])) { + $this->removeFilter($search); + unset($this->translations[$search]); + } + } + + foreach ($replacements as $search => $replace) { + if (!isset($this->translations[$search])) { + $this->addFilter( + $this->replacementFactory->createFilter($search, $replace), $search + ); + $this->translations[$search] = true; + } + } + } + + /** + * Get a line of output (including any CRLF). + * + * The $sequence number comes from any writes and may or may not be used + * depending upon the implementation. + * + * @param int $sequence of last write to scan from + * + * @return string + * + * @throws Swift_IoException + */ + public function readLine($sequence) + { + if (isset($this->out) && !feof($this->out)) { + $line = fgets($this->out); + if (0 == \strlen($line)) { + $metas = stream_get_meta_data($this->out); + if ($metas['timed_out']) { + throw new Swift_IoException('Connection to '.$this->getReadConnectionDescription().' Timed Out'); + } + } + + return $line; + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the remaining bytes are given instead. + * If no bytes are remaining at all, boolean false is returned. + * + * @param int $length + * + * @return string|bool + * + * @throws Swift_IoException + */ + public function read($length) + { + if (isset($this->out) && !feof($this->out)) { + $ret = fread($this->out, $length); + if (0 == \strlen($ret)) { + $metas = stream_get_meta_data($this->out); + if ($metas['timed_out']) { + throw new Swift_IoException('Connection to '.$this->getReadConnectionDescription().' Timed Out'); + } + } + + return $ret; + } + } + + /** Not implemented */ + public function setReadPointer($byteOffset) + { + } + + /** Flush the stream contents */ + protected function flush() + { + if (isset($this->in)) { + fflush($this->in); + } + } + + /** Write this bytes to the stream */ + protected function doCommit($bytes) + { + if (isset($this->in)) { + $bytesToWrite = \strlen($bytes); + $totalBytesWritten = 0; + + while ($totalBytesWritten < $bytesToWrite) { + $bytesWritten = fwrite($this->in, substr($bytes, $totalBytesWritten)); + if (false === $bytesWritten || 0 === $bytesWritten) { + break; + } + + $totalBytesWritten += $bytesWritten; + } + + if ($totalBytesWritten > 0) { + return ++$this->sequence; + } + } + } + + /** + * Establishes a connection to a remote server. + */ + private function establishSocketConnection() + { + $host = $this->params['host']; + if (!empty($this->params['protocol'])) { + $host = $this->params['protocol'].'://'.$host; + } + $timeout = 15; + if (!empty($this->params['timeout'])) { + $timeout = $this->params['timeout']; + } + $options = []; + if (!empty($this->params['sourceIp'])) { + $options['socket']['bindto'] = $this->params['sourceIp'].':0'; + } + + if (isset($this->params['stream_context_options'])) { + $options = array_merge($options, $this->params['stream_context_options']); + } + $streamContext = stream_context_create($options); + + set_error_handler(function ($type, $msg) { + throw new Swift_TransportException('Connection could not be established with host '.$this->params['host'].' :'.$msg); + }); + try { + $this->stream = stream_socket_client($host.':'.$this->params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext); + } finally { + restore_error_handler(); + } + + if (!empty($this->params['blocking'])) { + stream_set_blocking($this->stream, 1); + } else { + stream_set_blocking($this->stream, 0); + } + stream_set_timeout($this->stream, $timeout); + $this->in = &$this->stream; + $this->out = &$this->stream; + } + + /** + * Opens a process for input/output. + */ + private function establishProcessConnection() + { + $command = $this->params['command']; + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $pipes = []; + $this->stream = proc_open($command, $descriptorSpec, $pipes); + stream_set_blocking($pipes[2], 0); + if ($err = stream_get_contents($pipes[2])) { + throw new Swift_TransportException('Process could not be started ['.$err.']'); + } + $this->in = &$pipes[0]; + $this->out = &$pipes[1]; + } + + private function getReadConnectionDescription() + { + switch ($this->params['type']) { + case self::TYPE_PROCESS: + return 'Process '.$this->params['command']; + break; + + case self::TYPE_SOCKET: + default: + $host = $this->params['host']; + if (!empty($this->params['protocol'])) { + $host = $this->params['protocol'].'://'.$host; + } + $host .= ':'.$this->params['port']; + + return $host; + break; + } + } +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php new file mode 100644 index 0000000..c741745 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php @@ -0,0 +1,28 @@ +register('cache') + ->asAliasOf('cache.array') + + ->register('tempdir') + ->asValue('/tmp') + + ->register('cache.null') + ->asSharedInstanceOf('Swift_KeyCache_NullKeyCache') + + ->register('cache.array') + ->asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache') + ->withDependencies(['cache.inputstream']) + + ->register('cache.disk') + ->asSharedInstanceOf('Swift_KeyCache_DiskKeyCache') + ->withDependencies(['cache.inputstream', 'tempdir']) + + ->register('cache.inputstream') + ->asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream') +; diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php new file mode 100644 index 0000000..64d69d2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php @@ -0,0 +1,9 @@ +register('message.message') + ->asNewInstanceOf('Swift_Message') + + ->register('message.mimepart') + ->asNewInstanceOf('Swift_MimePart') +; diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php new file mode 100644 index 0000000..307756c --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php @@ -0,0 +1,134 @@ +register('properties.charset') + ->asValue('utf-8') + + ->register('email.validator') + ->asSharedInstanceOf('Egulias\EmailValidator\EmailValidator') + + ->register('mime.idgenerator.idright') + // As SERVER_NAME can come from the user in certain configurations, check that + // it does not contain forbidden characters (see RFC 952 and RFC 2181). Use + // preg_replace() instead of preg_match() to prevent DoS attacks with long host names. + ->asValue(!empty($_SERVER['SERVER_NAME']) && '' === preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated') + + ->register('mime.idgenerator') + ->asSharedInstanceOf('Swift_Mime_IdGenerator') + ->withDependencies([ + 'mime.idgenerator.idright', + ]) + + ->register('mime.message') + ->asNewInstanceOf('Swift_Mime_SimpleMessage') + ->withDependencies([ + 'mime.headerset', + 'mime.textcontentencoder', + 'cache', + 'mime.idgenerator', + 'properties.charset', + ]) + + ->register('mime.part') + ->asNewInstanceOf('Swift_Mime_MimePart') + ->withDependencies([ + 'mime.headerset', + 'mime.textcontentencoder', + 'cache', + 'mime.idgenerator', + 'properties.charset', + ]) + + ->register('mime.attachment') + ->asNewInstanceOf('Swift_Mime_Attachment') + ->withDependencies([ + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.idgenerator', + ]) + ->addConstructorValue($swift_mime_types) + + ->register('mime.embeddedfile') + ->asNewInstanceOf('Swift_Mime_EmbeddedFile') + ->withDependencies([ + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.idgenerator', + ]) + ->addConstructorValue($swift_mime_types) + + ->register('mime.headerfactory') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderFactory') + ->withDependencies([ + 'mime.qpheaderencoder', + 'mime.rfc2231encoder', + 'email.validator', + 'properties.charset', + 'address.idnaddressencoder', + ]) + + ->register('mime.headerset') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderSet') + ->withDependencies(['mime.headerfactory', 'properties.charset']) + + ->register('mime.qpheaderencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder') + ->withDependencies(['mime.charstream']) + + ->register('mime.base64headerencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_Base64HeaderEncoder') + ->withDependencies(['mime.charstream']) + + ->register('mime.charstream') + ->asNewInstanceOf('Swift_CharacterStream_NgCharacterStream') + ->withDependencies(['mime.characterreaderfactory', 'properties.charset']) + + ->register('mime.bytecanonicalizer') + ->asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter') + ->addConstructorValue([[0x0D, 0x0A], [0x0D], [0x0A]]) + ->addConstructorValue([[0x0A], [0x0A], [0x0D, 0x0A]]) + + ->register('mime.characterreaderfactory') + ->asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory') + + ->register('mime.textcontentencoder') + ->asAliasOf('mime.qpcontentencoder') + + ->register('mime.safeqpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(['mime.charstream', 'mime.bytecanonicalizer']) + + ->register('mime.rawcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_RawContentEncoder') + + ->register('mime.nativeqpcontentencoder') + ->withDependencies(['properties.charset']) + ->asNewInstanceOf('Swift_Mime_ContentEncoder_NativeQpContentEncoder') + + ->register('mime.qpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoderProxy') + ->withDependencies(['mime.safeqpcontentencoder', 'mime.nativeqpcontentencoder', 'properties.charset']) + + ->register('mime.7bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('7bit') + ->addConstructorValue(true) + + ->register('mime.8bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('8bit') + ->addConstructorValue(true) + + ->register('mime.base64contentencoder') + ->asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder') + + ->register('mime.rfc2231encoder') + ->asNewInstanceOf('Swift_Encoder_Rfc2231Encoder') + ->withDependencies(['mime.charstream']) +; + +unset($swift_mime_types); diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php new file mode 100644 index 0000000..34a63c7 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php @@ -0,0 +1,97 @@ +register('transport.localdomain') + // As SERVER_NAME can come from the user in certain configurations, check that + // it does not contain forbidden characters (see RFC 952 and RFC 2181). Use + // preg_replace() instead of preg_match() to prevent DoS attacks with long host names. + ->asValue(!empty($_SERVER['SERVER_NAME']) && '' === preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $_SERVER['SERVER_NAME']) ? trim($_SERVER['SERVER_NAME'], '[]') : '127.0.0.1') + + ->register('transport.smtp') + ->asNewInstanceOf('Swift_Transport_EsmtpTransport') + ->withDependencies([ + 'transport.buffer', + 'transport.smtphandlers', + 'transport.eventdispatcher', + 'transport.localdomain', + 'address.idnaddressencoder', + ]) + + ->register('transport.sendmail') + ->asNewInstanceOf('Swift_Transport_SendmailTransport') + ->withDependencies([ + 'transport.buffer', + 'transport.eventdispatcher', + 'transport.localdomain', + ]) + + ->register('transport.loadbalanced') + ->asNewInstanceOf('Swift_Transport_LoadBalancedTransport') + + ->register('transport.failover') + ->asNewInstanceOf('Swift_Transport_FailoverTransport') + + ->register('transport.spool') + ->asNewInstanceOf('Swift_Transport_SpoolTransport') + ->withDependencies(['transport.eventdispatcher']) + + ->register('transport.null') + ->asNewInstanceOf('Swift_Transport_NullTransport') + ->withDependencies(['transport.eventdispatcher']) + + ->register('transport.buffer') + ->asNewInstanceOf('Swift_Transport_StreamBuffer') + ->withDependencies(['transport.replacementfactory']) + + ->register('transport.smtphandlers') + ->asArray() + ->withDependencies(['transport.authhandler']) + + ->register('transport.authhandler') + ->asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler') + ->withDependencies(['transport.authhandlers']) + + ->register('transport.authhandlers') + ->asArray() + ->withDependencies([ + 'transport.crammd5auth', + 'transport.loginauth', + 'transport.plainauth', + 'transport.ntlmauth', + 'transport.xoauth2auth', + ]) + + ->register('transport.smtputf8handler') + ->asNewInstanceOf('Swift_Transport_Esmtp_SmtpUtf8Handler') + + ->register('transport.8bitmimehandler') + ->asNewInstanceOf('Swift_Transport_Esmtp_EightBitMimeHandler') + ->addConstructorValue('8BITMIME') + + ->register('transport.crammd5auth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator') + + ->register('transport.loginauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator') + + ->register('transport.plainauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator') + + ->register('transport.xoauth2auth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_XOAuth2Authenticator') + + ->register('transport.ntlmauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_NTLMAuthenticator') + + ->register('transport.eventdispatcher') + ->asNewInstanceOf('Swift_Events_SimpleEventDispatcher') + + ->register('transport.replacementfactory') + ->asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory') + + ->register('address.idnaddressencoder') + ->asNewInstanceOf('Swift_AddressEncoder_IdnAddressEncoder') + + ->register('address.utf8addressencoder') + ->asNewInstanceOf('Swift_AddressEncoder_Utf8AddressEncoder') +; diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/mime_types.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/mime_types.php new file mode 100644 index 0000000..72c6fd2 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/mime_types.php @@ -0,0 +1,1007 @@ + 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'appcache' => 'text/cache-manifest', + 'apr' => 'application/vnd.lotus-approach', + 'aps' => 'application/postscript', + 'arc' => 'application/x-freearc', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/x-apple-diskimage', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'application/x-msmetafile', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/x-gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'jsonml' => 'application/jsonml+json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/x-lzh-compressed', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/x-lzh-compressed', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mar' => 'application/octet-stream', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'ntf' => 'application/vnd.nitf', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obj' => 'application/x-tgif', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'application/x-php', + 'php3' => 'application/x-php', + 'php4' => 'application/x-php', + 'php5' => 'application/x-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sid' => 'image/x-mrsid-image', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tga' => 'image/x-tga', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'application/font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+binary', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d+vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/x-xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + '123' => 'application/vnd.lotus-1-2-3', +]; diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/preferences.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/preferences.php new file mode 100644 index 0000000..27b7065 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/preferences.php @@ -0,0 +1,19 @@ +setCharset('utf-8'); + +// Without these lines the default caching mechanism is "array" but this uses a lot of memory. +// If possible, use a disk cache to enable attaching large attachments etc. +// You can override the default temporary directory by setting the TMPDIR environment variable. +if (@is_writable($tmpDir = sys_get_temp_dir())) { + $preferences->setTempDir($tmpDir)->setCacheType('disk'); +} diff --git a/plugins/email/vendor/swiftmailer/swiftmailer/lib/swift_required.php b/plugins/email/vendor/swiftmailer/swiftmailer/lib/swift_required.php new file mode 100644 index 0000000..d696056 --- /dev/null +++ b/plugins/email/vendor/swiftmailer/swiftmailer/lib/swift_required.php @@ -0,0 +1,22 @@ + 'application/x-php', + 'php3' => 'application/x-php', + 'php4' => 'application/x-php', + 'php5' => 'application/x-php', + 'zip' => 'application/zip', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'css' => 'text/css', + 'js' => 'text/javascript', + 'txt' => 'text/plain', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'avi' => 'video/avi', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bz2', + 'csv' => 'text/csv', + 'dmg' => 'application/x-apple-diskimage', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'eml' => 'message/rfc822', + 'aps' => 'application/postscript', + 'exe' => 'application/x-ms-dos-executable', + 'flv' => 'video/x-flv', + 'gz' => 'application/x-gzip', + 'hqx' => 'application/stuffit', + 'htm' => 'text/html', + 'html' => 'text/html', + 'jar' => 'application/x-java-archive', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'm3u' => 'audio/x-mpegurl', + 'm4a' => 'audio/mp4', + 'mdb' => 'application/x-msaccess', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'odg' => 'vnd.oasis.opendocument.graphics', + 'odp' => 'vnd.oasis.opendocument.presentation', + 'odt' => 'vnd.oasis.opendocument.text', + 'ods' => 'vnd.oasis.opendocument.spreadsheet', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'rar' => 'application/x-rar-compressed', + 'rtf' => 'application/rtf', + 'tar' => 'application/x-tar', + 'sit' => 'application/x-stuffit', + 'svg' => 'image/svg+xml', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ttf' => 'application/x-font-truetype', + 'vcf' => 'text/x-vcard', + 'wav' => 'audio/wav', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'audio/x-ms-wmv', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + ]; + + // wrap array for generating file + foreach ($valid_mime_types_preset as $extension => $mime_type) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + + // all extensions from second match + foreach ($matches[2] as $i => $extensions) { + // explode multiple extensions from string + $extensions = explode(' ', strtolower($extensions)); + + // force array for foreach + if (!\is_array($extensions)) { + $extensions = [$extensions]; + } + + foreach ($extensions as $extension) { + // get mime type + $mime_type = $matches[1][$i]; + + // check if string length lower than 10 + if (\strlen($extension) < 10) { + if (!isset($valid_mime_types[$mime_type])) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + } + } + } + } + + $xml = simplexml_load_string($mime_xml); + + foreach ($xml as $node) { + // check if there is no pattern + if (!isset($node->glob['pattern'])) { + continue; + } + + // get all matching extensions from match + foreach ((array) $node->glob['pattern'] as $extension) { + // skip none glob extensions + if (false === strpos($extension, '.')) { + continue; + } + + // remove get only last part + $extension = explode('.', strtolower($extension)); + $extension = end($extension); + } + + if (isset($node->glob['pattern'][0])) { + // mime type + $mime_type = strtolower((string) $node['type']); + + // get first extension + $extension = strtolower(trim($node->glob['ddpattern'][0], '*.')); + + // skip none glob extensions and check if string length between 1 and 10 + if (false !== strpos($extension, '.') || \strlen($extension) < 1 || \strlen($extension) > 9) { + continue; + } + + // check if string length lower than 10 + if (!isset($valid_mime_types[$mime_type])) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + } + } + + // full list of valid extensions only + $valid_mime_types = array_unique($valid_mime_types); + ksort($valid_mime_types); + + // combine mime types and extensions array + $output = "$preamble\$swift_mime_types = array(\n ".implode(",\n ", $valid_mime_types)."\n);"; + + // write mime_types.php config file + @file_put_contents('./mime_types.php', $output); +} + +generateUpToDateMimeArray(); diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Iconv.php b/plugins/email/vendor/symfony/polyfill-iconv/Iconv.php new file mode 100644 index 0000000..c17a70d --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/Iconv.php @@ -0,0 +1,744 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Iconv; + +/** + * iconv implementation in pure PHP, UTF-8 centric. + * + * Implemented: + * - iconv - Convert string to requested character encoding + * - iconv_mime_decode - Decodes a MIME header field + * - iconv_mime_decode_headers - Decodes multiple MIME header fields at once + * - iconv_get_encoding - Retrieve internal configuration variables of iconv extension + * - iconv_set_encoding - Set current setting for character encoding conversion + * - iconv_mime_encode - Composes a MIME header field + * - iconv_strlen - Returns the character count of string + * - iconv_strpos - Finds position of first occurrence of a needle within a haystack + * - iconv_strrpos - Finds the last occurrence of a needle within a haystack + * - iconv_substr - Cut out part of a string + * + * Charsets available for conversion are defined by files + * in the charset/ directory and by Iconv::$alias below. + * You're welcome to send back any addition you make. + * + * @author Nicolas Grekas + * + * @internal + */ +final class Iconv +{ + public const ERROR_ILLEGAL_CHARACTER = 'iconv(): Detected an illegal character in input string'; + public const ERROR_WRONG_CHARSET = 'iconv(): Wrong charset, conversion from `%s\' to `%s\' is not allowed'; + + public static $inputEncoding = 'utf-8'; + public static $outputEncoding = 'utf-8'; + public static $internalEncoding = 'utf-8'; + + private static $alias = [ + 'utf8' => 'utf-8', + 'ascii' => 'us-ascii', + 'tis-620' => 'iso-8859-11', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'shift-jis' => 'cp932', + 'shift_jis' => 'cp932', + 'latin1' => 'iso-8859-1', + 'latin2' => 'iso-8859-2', + 'latin3' => 'iso-8859-3', + 'latin4' => 'iso-8859-4', + 'latin5' => 'iso-8859-9', + 'latin6' => 'iso-8859-10', + 'latin7' => 'iso-8859-13', + 'latin8' => 'iso-8859-14', + 'latin9' => 'iso-8859-15', + 'latin10' => 'iso-8859-16', + 'iso8859-1' => 'iso-8859-1', + 'iso8859-2' => 'iso-8859-2', + 'iso8859-3' => 'iso-8859-3', + 'iso8859-4' => 'iso-8859-4', + 'iso8859-5' => 'iso-8859-5', + 'iso8859-6' => 'iso-8859-6', + 'iso8859-7' => 'iso-8859-7', + 'iso8859-8' => 'iso-8859-8', + 'iso8859-9' => 'iso-8859-9', + 'iso8859-10' => 'iso-8859-10', + 'iso8859-11' => 'iso-8859-11', + 'iso8859-12' => 'iso-8859-12', + 'iso8859-13' => 'iso-8859-13', + 'iso8859-14' => 'iso-8859-14', + 'iso8859-15' => 'iso-8859-15', + 'iso8859-16' => 'iso-8859-16', + 'iso_8859-1' => 'iso-8859-1', + 'iso_8859-2' => 'iso-8859-2', + 'iso_8859-3' => 'iso-8859-3', + 'iso_8859-4' => 'iso-8859-4', + 'iso_8859-5' => 'iso-8859-5', + 'iso_8859-6' => 'iso-8859-6', + 'iso_8859-7' => 'iso-8859-7', + 'iso_8859-8' => 'iso-8859-8', + 'iso_8859-9' => 'iso-8859-9', + 'iso_8859-10' => 'iso-8859-10', + 'iso_8859-11' => 'iso-8859-11', + 'iso_8859-12' => 'iso-8859-12', + 'iso_8859-13' => 'iso-8859-13', + 'iso_8859-14' => 'iso-8859-14', + 'iso_8859-15' => 'iso-8859-15', + 'iso_8859-16' => 'iso-8859-16', + 'iso88591' => 'iso-8859-1', + 'iso88592' => 'iso-8859-2', + 'iso88593' => 'iso-8859-3', + 'iso88594' => 'iso-8859-4', + 'iso88595' => 'iso-8859-5', + 'iso88596' => 'iso-8859-6', + 'iso88597' => 'iso-8859-7', + 'iso88598' => 'iso-8859-8', + 'iso88599' => 'iso-8859-9', + 'iso885910' => 'iso-8859-10', + 'iso885911' => 'iso-8859-11', + 'iso885912' => 'iso-8859-12', + 'iso885913' => 'iso-8859-13', + 'iso885914' => 'iso-8859-14', + 'iso885915' => 'iso-8859-15', + 'iso885916' => 'iso-8859-16', + ]; + private static $translitMap = []; + private static $convertMap = []; + private static $errorHandler; + private static $lastError; + + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $isValidUtf8; + + public static function iconv($inCharset, $outCharset, $str) + { + $str = (string) $str; + if ('' === $str) { + return ''; + } + + // Prepare for //IGNORE and //TRANSLIT + + $translit = $ignore = ''; + + $outCharset = strtolower($outCharset); + $inCharset = strtolower($inCharset); + + if ('' === $outCharset) { + $outCharset = 'iso-8859-1'; + } + if ('' === $inCharset) { + $inCharset = 'iso-8859-1'; + } + + do { + $loop = false; + + if ('//translit' === substr($outCharset, -10)) { + $loop = $translit = true; + $outCharset = substr($outCharset, 0, -10); + } + + if ('//ignore' === substr($outCharset, -8)) { + $loop = $ignore = true; + $outCharset = substr($outCharset, 0, -8); + } + } while ($loop); + + do { + $loop = false; + + if ('//translit' === substr($inCharset, -10)) { + $loop = true; + $inCharset = substr($inCharset, 0, -10); + } + + if ('//ignore' === substr($inCharset, -8)) { + $loop = true; + $inCharset = substr($inCharset, 0, -8); + } + } while ($loop); + + if (isset(self::$alias[$inCharset])) { + $inCharset = self::$alias[$inCharset]; + } + if (isset(self::$alias[$outCharset])) { + $outCharset = self::$alias[$outCharset]; + } + + // Load charset maps + + if (('utf-8' !== $inCharset && !self::loadMap('from.', $inCharset, $inMap)) + || ('utf-8' !== $outCharset && !self::loadMap('to.', $outCharset, $outMap))) { + trigger_error(sprintf(self::ERROR_WRONG_CHARSET, $inCharset, $outCharset)); + + return false; + } + + if ('utf-8' !== $inCharset) { + // Convert input to UTF-8 + $result = ''; + if (self::mapToUtf8($result, $inMap, $str, $ignore)) { + $str = $result; + } else { + $str = false; + } + self::$isValidUtf8 = true; + } else { + self::$isValidUtf8 = preg_match('//u', $str); + + if (!self::$isValidUtf8 && !$ignore) { + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } + + if ('utf-8' === $outCharset) { + // UTF-8 validation + $str = self::utf8ToUtf8($str, $ignore); + } + } + + if ('utf-8' !== $outCharset && false !== $str) { + // Convert output to UTF-8 + $result = ''; + if (self::mapFromUtf8($result, $outMap, $str, $ignore, $translit)) { + return $result; + } + + return false; + } + + return $str; + } + + public static function iconv_mime_decode_headers($str, $mode = 0, $charset = null) + { + if (null === $charset) { + $charset = self::$internalEncoding; + } + + if (false !== strpos($str, "\r")) { + $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n"); + } + $str = explode("\n\n", $str, 2); + + $headers = []; + + $str = preg_split('/\n(?![ \t])/', $str[0]); + foreach ($str as $str) { + $str = self::iconv_mime_decode($str, $mode, $charset); + if (false === $str) { + return false; + } + $str = explode(':', $str, 2); + + if (2 === \count($str)) { + if (isset($headers[$str[0]])) { + if (!\is_array($headers[$str[0]])) { + $headers[$str[0]] = [$headers[$str[0]]]; + } + $headers[$str[0]][] = ltrim($str[1]); + } else { + $headers[$str[0]] = ltrim($str[1]); + } + } + } + + return $headers; + } + + public static function iconv_mime_decode($str, $mode = 0, $charset = null) + { + if (null === $charset) { + $charset = self::$internalEncoding; + } + if (\ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) { + $charset .= '//IGNORE'; + } + + if (false !== strpos($str, "\r")) { + $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n"); + } + $str = preg_split('/\n(?![ \t])/', rtrim($str), 2); + $str = preg_replace('/[ \t]*\n[ \t]+/', ' ', rtrim($str[0])); + $str = preg_split('/=\?([^?]+)\?([bqBQ])\?(.*?)\?=/', $str, -1, \PREG_SPLIT_DELIM_CAPTURE); + + $result = self::iconv('utf-8', $charset, $str[0]); + if (false === $result) { + return false; + } + + $i = 1; + $len = \count($str); + + while ($i < $len) { + $c = strtolower($str[$i]); + if ((\ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) + && 'utf-8' !== $c + && !isset(self::$alias[$c]) + && !self::loadMap('from.', $c, $d)) { + $d = false; + } elseif ('B' === strtoupper($str[$i + 1])) { + $d = base64_decode($str[$i + 2]); + } else { + $d = rawurldecode(strtr(str_replace('%', '%25', $str[$i + 2]), '=_', '% ')); + } + + if (false !== $d) { + if ('' !== $d) { + if ('' === $d = self::iconv($c, $charset, $d)) { + $str[$i + 3] = substr($str[$i + 3], 1); + } else { + $result .= $d; + } + } + $d = self::iconv('utf-8', $charset, $str[$i + 3]); + if ('' !== trim($d)) { + $result .= $d; + } + } elseif (\ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) { + $result .= "=?{$str[$i]}?{$str[$i + 1]}?{$str[$i + 2]}?={$str[$i + 3]}"; + } else { + $result = false; + break; + } + + $i += 4; + } + + return $result; + } + + public static function iconv_get_encoding($type = 'all') + { + switch ($type) { + case 'input_encoding': return self::$inputEncoding; + case 'output_encoding': return self::$outputEncoding; + case 'internal_encoding': return self::$internalEncoding; + } + + return [ + 'input_encoding' => self::$inputEncoding, + 'output_encoding' => self::$outputEncoding, + 'internal_encoding' => self::$internalEncoding, + ]; + } + + public static function iconv_set_encoding($type, $charset) + { + switch ($type) { + case 'input_encoding': self::$inputEncoding = $charset; break; + case 'output_encoding': self::$outputEncoding = $charset; break; + case 'internal_encoding': self::$internalEncoding = $charset; break; + default: return false; + } + + return true; + } + + public static function iconv_mime_encode($fieldName, $fieldValue, $pref = null) + { + if (!\is_array($pref)) { + $pref = []; + } + + $pref += [ + 'scheme' => 'B', + 'input-charset' => self::$internalEncoding, + 'output-charset' => self::$internalEncoding, + 'line-length' => 76, + 'line-break-chars' => "\r\n", + ]; + + if (preg_match('/[\x80-\xFF]/', $fieldName)) { + $fieldName = ''; + } + + $scheme = strtoupper(substr($pref['scheme'], 0, 1)); + $in = strtolower($pref['input-charset']); + $out = strtolower($pref['output-charset']); + + if ('utf-8' !== $in && false === $fieldValue = self::iconv($in, 'utf-8', $fieldValue)) { + return false; + } + + preg_match_all('/./us', $fieldValue, $chars); + + $chars = $chars[0] ?? []; + + $lineBreak = (int) $pref['line-length']; + $lineStart = "=?{$pref['output-charset']}?{$scheme}?"; + $lineLength = \strlen($fieldName) + 2 + \strlen($lineStart) + 2; + $lineOffset = \strlen($lineStart) + 3; + $lineData = ''; + + $fieldValue = []; + + $Q = 'Q' === $scheme; + + foreach ($chars as $c) { + if ('utf-8' !== $out && false === $c = self::iconv('utf-8', $out, $c)) { + return false; + } + + $o = $Q + ? $c = preg_replace_callback( + '/[=_\?\x00-\x1F\x80-\xFF]/', + [__CLASS__, 'qpByteCallback'], + $c + ) + : base64_encode($lineData.$c); + + if (isset($o[$lineBreak - $lineLength])) { + if (!$Q) { + $lineData = base64_encode($lineData); + } + $fieldValue[] = $lineStart.$lineData.'?='; + $lineLength = $lineOffset; + $lineData = ''; + } + + $lineData .= $c; + $Q && $lineLength += \strlen($c); + } + + if ('' !== $lineData) { + if (!$Q) { + $lineData = base64_encode($lineData); + } + $fieldValue[] = $lineStart.$lineData.'?='; + } + + return $fieldName.': '.implode($pref['line-break-chars'].' ', $fieldValue); + } + + public static function iconv_strlen($s, $encoding = null) + { + static $hasXml = null; + if (null === $hasXml) { + $hasXml = \extension_loaded('xml'); + } + + if ($hasXml) { + return self::strlen1($s, $encoding); + } + + return self::strlen2($s, $encoding); + } + + public static function strlen1($s, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8') && false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + return \strlen(utf8_decode($s)); + } + + public static function strlen2($s, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8') && false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + $ulenMask = self::$ulenMask; + + $i = 0; + $j = 0; + $len = \strlen($s); + + while ($i < $len) { + $u = $s[$i] & "\xF0"; + $i += $ulenMask[$u] ?? 1; + ++$j; + } + + return $j; + } + + public static function iconv_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + + if (0 !== stripos($encoding, 'utf-8')) { + if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) { + return false; + } + if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) { + return false; + } + } + + if ($offset = (int) $offset) { + $haystack = self::iconv_substr($haystack, $offset, 2147483647, 'utf-8'); + } + $pos = strpos($haystack, $needle); + + return false === $pos ? false : ($offset + ($pos ? self::iconv_strlen(substr($haystack, 0, $pos), 'utf-8') : 0)); + } + + public static function iconv_strrpos($haystack, $needle, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + + if (0 !== stripos($encoding, 'utf-8')) { + if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) { + return false; + } + if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) { + return false; + } + } + + $pos = isset($needle[0]) ? strrpos($haystack, $needle) : false; + + return false === $pos ? false : self::iconv_strlen($pos ? substr($haystack, 0, $pos) : $haystack, 'utf-8'); + } + + public static function iconv_substr($s, $start, $length = 2147483647, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8')) { + $encoding = null; + } elseif (false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + $s = (string) $s; + $slen = self::iconv_strlen($s, 'utf-8'); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + if (\PHP_VERSION_ID < 80000) { + return false; + } + + $start = 0; + } + if ($start >= $slen) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + $rx = $slen - $start; + + if (0 > $length) { + $length += $rx; + } + if (0 === $length) { + return ''; + } + if (0 > $length) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + if ($length > $rx) { + $length = $rx; + } + + $rx = '/^'.($start ? self::pregOffset($start) : '').'('.self::pregOffset($length).')/u'; + + $s = preg_match($rx, $s, $s) ? $s[1] : ''; + + if (null === $encoding) { + return $s; + } + + return self::iconv('utf-8', $encoding, $s); + } + + private static function loadMap($type, $charset, &$map) + { + if (!isset(self::$convertMap[$type.$charset])) { + if (false === $map = self::getData($type.$charset)) { + if ('to.' === $type && self::loadMap('from.', $charset, $map)) { + $map = array_flip($map); + } else { + return false; + } + } + + self::$convertMap[$type.$charset] = $map; + } else { + $map = self::$convertMap[$type.$charset]; + } + + return true; + } + + private static function utf8ToUtf8($str, $ignore) + { + $ulenMask = self::$ulenMask; + $valid = self::$isValidUtf8; + + $u = $str; + $i = $j = 0; + $len = \strlen($str); + + while ($i < $len) { + if ($str[$i] < "\x80") { + $u[$j++] = $str[$i++]; + } else { + $ulen = $str[$i] & "\xF0"; + $ulen = $ulenMask[$ulen] ?? 1; + $uchr = substr($str, $i, $ulen); + + if (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr))) { + if ($ignore) { + ++$i; + continue; + } + + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } + + $i += $ulen; + + $u[$j++] = $uchr[0]; + + isset($uchr[1]) && 0 !== ($u[$j++] = $uchr[1]) + && isset($uchr[2]) && 0 !== ($u[$j++] = $uchr[2]) + && isset($uchr[3]) && 0 !== ($u[$j++] = $uchr[3]); + } + } + + return substr($u, 0, $j); + } + + private static function mapToUtf8(&$result, array $map, $str, $ignore) + { + $len = \strlen($str); + for ($i = 0; $i < $len; ++$i) { + if (isset($str[$i + 1], $map[$str[$i].$str[$i + 1]])) { + $result .= $map[$str[$i].$str[++$i]]; + } elseif (isset($map[$str[$i]])) { + $result .= $map[$str[$i]]; + } elseif (!$ignore) { + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } + } + + return true; + } + + private static function mapFromUtf8(&$result, array $map, $str, $ignore, $translit) + { + $ulenMask = self::$ulenMask; + $valid = self::$isValidUtf8; + + if ($translit && !self::$translitMap) { + self::$translitMap = self::getData('translit'); + } + + $i = 0; + $len = \strlen($str); + + while ($i < $len) { + if ($str[$i] < "\x80") { + $uchr = $str[$i++]; + } else { + $ulen = $str[$i] & "\xF0"; + $ulen = $ulenMask[$ulen] ?? 1; + $uchr = substr($str, $i, $ulen); + + if ($ignore && (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr)))) { + ++$i; + continue; + } + + $i += $ulen; + } + + if (isset($map[$uchr])) { + $result .= $map[$uchr]; + } elseif ($translit) { + if (isset(self::$translitMap[$uchr])) { + $uchr = self::$translitMap[$uchr]; + } elseif ($uchr >= "\xC3\x80") { + $uchr = \Normalizer::normalize($uchr, \Normalizer::NFD); + + if ($uchr[0] < "\x80") { + $uchr = $uchr[0]; + } elseif ($ignore) { + continue; + } else { + return false; + } + } elseif ($ignore) { + continue; + } else { + return false; + } + + $str = $uchr.substr($str, $i); + $len = \strlen($str); + $i = 0; + } elseif (!$ignore) { + return false; + } + } + + return true; + } + + private static function qpByteCallback(array $m) + { + return '='.strtoupper(dechex(\ord($m[0]))); + } + + private static function pregOffset($offset) + { + $rx = []; + $offset = (int) $offset; + + while ($offset > 65535) { + $rx[] = '.{65535}'; + $offset -= 65535; + } + + return implode('', $rx).'.{'.$offset.'}'; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/charset/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-iconv/LICENSE b/plugins/email/vendor/symfony/polyfill-iconv/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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/plugins/email/vendor/symfony/polyfill-iconv/README.md b/plugins/email/vendor/symfony/polyfill-iconv/README.md new file mode 100644 index 0000000..b0c8984 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Iconv +======================== + +This component provides a native PHP implementation of the +[php.net/iconv](https://php.net/iconv) functions +(short of [`ob_iconv_handler`](https://php.net/ob-iconv-handler)). + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.big5.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.big5.php new file mode 100644 index 0000000..b119854 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.big5.php @@ -0,0 +1,13719 @@ + ' ', + 'A' => ',', + 'B' => '、', + 'C' => '。', + 'D' => '.', + 'E' => '•', + 'F' => ';', + 'G' => ':', + 'H' => '?', + 'I' => '!', + 'J' => '︰', + 'K' => '…', + 'L' => '‥', + 'M' => '﹐', + 'N' => '、', + 'O' => '﹒', + 'P' => '·', + 'Q' => '﹔', + 'R' => '﹕', + 'S' => '﹖', + 'T' => '﹗', + 'U' => '|', + 'V' => '–', + 'W' => '︱', + 'X' => '—', + 'Y' => '︳', + 'Z' => '�', + '[' => '︴', + '\\' => '﹏', + ']' => '(', + '^' => ')', + '_' => '︵', + '`' => '︶', + 'a' => '{', + 'b' => '}', + 'c' => '︷', + 'd' => '︸', + 'e' => '〔', + 'f' => '〕', + 'g' => '︹', + 'h' => '︺', + 'i' => '【', + 'j' => '】', + 'k' => '︻', + 'l' => '︼', + 'm' => '《', + 'n' => '》', + 'o' => '︽', + 'p' => '︾', + 'q' => '〈', + 'r' => '〉', + 's' => '︿', + 't' => '﹀', + 'u' => '「', + 'v' => '」', + 'w' => '﹁', + 'x' => '﹂', + 'y' => '『', + 'z' => '』', + '{' => '﹃', + '|' => '﹄', + '}' => '﹙', + '~' => '﹚', + '' => '﹛', + '' => '﹜', + '' => '﹝', + '' => '﹞', + '' => '‘', + '' => '’', + '' => '“', + '' => '”', + '' => '〝', + '' => '〞', + '' => '‵', + '' => '′', + '' => '#', + '' => '&', + '' => '*', + '' => '※', + '' => '§', + '' => '〃', + '' => '○', + '' => '●', + '' => '△', + '' => '▲', + '' => '◎', + '' => '☆', + '' => '★', + '' => '◇', + '' => '◆', + '' => '□', + '' => '■', + '' => '▽', + '' => '▼', + '' => '㊣', + '' => '℅', + '' => '‾', + '' => '�', + '' => '_', + '' => '�', + '' => '﹉', + '' => '﹊', + '' => '﹍', + '' => '﹎', + '' => '﹋', + '' => '﹌', + '' => '﹟', + '' => '﹠', + '' => '﹡', + '' => '+', + '' => '-', + '' => '×', + '' => '÷', + '' => '±', + '' => '√', + '' => '<', + '' => '>', + '' => '=', + '' => '≦', + '' => '≧', + '' => '≠', + '' => '∞', + '' => '≒', + '' => '≡', + '' => '﹢', + '' => '﹣', + '' => '﹤', + '' => '﹥', + '' => '﹦', + '' => '∼', + '' => '∩', + '' => '∪', + '' => '⊥', + '' => '∠', + '' => '∟', + '' => '⊿', + '' => '㏒', + '' => '㏑', + '' => '∫', + '' => '∮', + '' => '∵', + '' => '∴', + '' => '♀', + '' => '♂', + '' => '♁', + '' => '☉', + '' => '↑', + '' => '↓', + '' => '←', + '' => '→', + '' => '↖', + '' => '↗', + '' => '↙', + '' => '↘', + '' => '∥', + '' => '∣', + '' => '�', + '@' => '�', + 'A' => '/', + 'B' => '\', + 'C' => '$', + 'D' => '¥', + 'E' => '〒', + 'F' => '¢', + 'G' => '£', + 'H' => '%', + 'I' => '@', + 'J' => '℃', + 'K' => '℉', + 'L' => '﹩', + 'M' => '﹪', + 'N' => '﹫', + 'O' => '㏕', + 'P' => '㎜', + 'Q' => '㎝', + 'R' => '㎞', + 'S' => '㏎', + 'T' => '㎡', + 'U' => '㎎', + 'V' => '㎏', + 'W' => '㏄', + 'X' => '°', + 'Y' => '兙', + 'Z' => '兛', + '[' => '兞', + '\\' => '兝', + ']' => '兡', + '^' => '兣', + '_' => '嗧', + '`' => '瓩', + 'a' => '糎', + 'b' => '▁', + 'c' => '▂', + 'd' => '▃', + 'e' => '▄', + 'f' => '▅', + 'g' => '▆', + 'h' => '▇', + 'i' => '█', + 'j' => '▏', + 'k' => '▎', + 'l' => '▍', + 'm' => '▌', + 'n' => '▋', + 'o' => '▊', + 'p' => '▉', + 'q' => '┼', + 'r' => '┴', + 's' => '┬', + 't' => '┤', + 'u' => '├', + 'v' => '▔', + 'w' => '─', + 'x' => '│', + 'y' => '▕', + 'z' => '┌', + '{' => '┐', + '|' => '└', + '}' => '┘', + '~' => '╭', + '' => '╮', + '' => '╰', + '' => '╯', + '' => '═', + '' => '╞', + '' => '╪', + '' => '╡', + '' => '◢', + '' => '◣', + '' => '◥', + '' => '◤', + '' => '╱', + '' => '╲', + '' => '╳', + '' => '0', + '' => '1', + '' => '2', + '' => '3', + '' => '4', + '' => '5', + '' => '6', + '' => '7', + '' => '8', + '' => '9', + '' => 'Ⅰ', + '' => 'Ⅱ', + '' => 'Ⅲ', + '' => 'Ⅳ', + '' => 'Ⅴ', + '' => 'Ⅵ', + '' => 'Ⅶ', + '' => 'Ⅷ', + '' => 'Ⅸ', + '' => 'Ⅹ', + '' => '〡', + '' => '〢', + '' => '〣', + '' => '〤', + '' => '〥', + '' => '〦', + '' => '〧', + '' => '〨', + '' => '〩', + '' => '�', + '' => '卄', + '' => '�', + '' => 'A', + '' => 'B', + '' => 'C', + '' => 'D', + '' => 'E', + '' => 'F', + '' => 'G', + '' => 'H', + '' => 'I', + '' => 'J', + '' => 'K', + '' => 'L', + '' => 'M', + '' => 'N', + '' => 'O', + '' => 'P', + '' => 'Q', + '' => 'R', + '' => 'S', + '' => 'T', + '' => 'U', + '' => 'V', + '' => 'W', + '' => 'X', + '' => 'Y', + '' => 'Z', + '' => 'a', + '' => 'b', + '' => 'c', + '' => 'd', + '' => 'e', + '' => 'f', + '' => 'g', + '' => 'h', + '' => 'i', + '' => 'j', + '' => 'k', + '' => 'l', + '' => 'm', + '' => 'n', + '' => 'o', + '' => 'p', + '' => 'q', + '' => 'r', + '' => 's', + '' => 't', + '' => 'u', + '' => 'v', + '@' => 'w', + 'A' => 'x', + 'B' => 'y', + 'C' => 'z', + 'D' => 'Α', + 'E' => 'Β', + 'F' => 'Γ', + 'G' => 'Δ', + 'H' => 'Ε', + 'I' => 'Ζ', + 'J' => 'Η', + 'K' => 'Θ', + 'L' => 'Ι', + 'M' => 'Κ', + 'N' => 'Λ', + 'O' => 'Μ', + 'P' => 'Ν', + 'Q' => 'Ξ', + 'R' => 'Ο', + 'S' => 'Π', + 'T' => 'Ρ', + 'U' => 'Σ', + 'V' => 'Τ', + 'W' => 'Υ', + 'X' => 'Φ', + 'Y' => 'Χ', + 'Z' => 'Ψ', + '[' => 'Ω', + '\\' => 'α', + ']' => 'β', + '^' => 'γ', + '_' => 'δ', + '`' => 'ε', + 'a' => 'ζ', + 'b' => 'η', + 'c' => 'θ', + 'd' => 'ι', + 'e' => 'κ', + 'f' => 'λ', + 'g' => 'μ', + 'h' => 'ν', + 'i' => 'ξ', + 'j' => 'ο', + 'k' => 'π', + 'l' => 'ρ', + 'm' => 'σ', + 'n' => 'τ', + 'o' => 'υ', + 'p' => 'φ', + 'q' => 'χ', + 'r' => 'ψ', + 's' => 'ω', + 't' => 'ㄅ', + 'u' => 'ㄆ', + 'v' => 'ㄇ', + 'w' => 'ㄈ', + 'x' => 'ㄉ', + 'y' => 'ㄊ', + 'z' => 'ㄋ', + '{' => 'ㄌ', + '|' => 'ㄍ', + '}' => 'ㄎ', + '~' => 'ㄏ', + '' => 'ㄐ', + '' => 'ㄑ', + '' => 'ㄒ', + '' => 'ㄓ', + '' => 'ㄔ', + '' => 'ㄕ', + '' => 'ㄖ', + '' => 'ㄗ', + '' => 'ㄘ', + '' => 'ㄙ', + '' => 'ㄚ', + '' => 'ㄛ', + '' => 'ㄜ', + '' => 'ㄝ', + '' => 'ㄞ', + '' => 'ㄟ', + '' => 'ㄠ', + '' => 'ㄡ', + '' => 'ㄢ', + '' => 'ㄣ', + '' => 'ㄤ', + '' => 'ㄥ', + '' => 'ㄦ', + '' => 'ㄧ', + '' => 'ㄨ', + '' => 'ㄩ', + '' => '˙', + '' => 'ˉ', + '' => 'ˊ', + '' => 'ˇ', + '' => 'ˋ', + '@' => '一', + 'A' => '乙', + 'B' => '丁', + 'C' => '七', + 'D' => '乃', + 'E' => '九', + 'F' => '了', + 'G' => '二', + 'H' => '人', + 'I' => '儿', + 'J' => '入', + 'K' => '八', + 'L' => '几', + 'M' => '刀', + 'N' => '刁', + 'O' => '力', + 'P' => '匕', + 'Q' => '十', + 'R' => '卜', + 'S' => '又', + 'T' => '三', + 'U' => '下', + 'V' => '丈', + 'W' => '上', + 'X' => '丫', + 'Y' => '丸', + 'Z' => '凡', + '[' => '久', + '\\' => '么', + ']' => '也', + '^' => '乞', + '_' => '于', + '`' => '亡', + 'a' => '兀', + 'b' => '刃', + 'c' => '勺', + 'd' => '千', + 'e' => '叉', + 'f' => '口', + 'g' => '土', + 'h' => '士', + 'i' => '夕', + 'j' => '大', + 'k' => '女', + 'l' => '子', + 'm' => '孑', + 'n' => '孓', + 'o' => '寸', + 'p' => '小', + 'q' => '尢', + 'r' => '尸', + 's' => '山', + 't' => '川', + 'u' => '工', + 'v' => '己', + 'w' => '已', + 'x' => '巳', + 'y' => '巾', + 'z' => '干', + '{' => '廾', + '|' => '弋', + '}' => '弓', + '~' => '才', + '' => '丑', + '' => '丐', + '' => '不', + '' => '中', + '' => '丰', + '' => '丹', + '' => '之', + '' => '尹', + '' => '予', + '' => '云', + '' => '井', + '' => '互', + '' => '五', + '' => '亢', + '' => '仁', + '' => '什', + '' => '仃', + '' => '仆', + '' => '仇', + '' => '仍', + '' => '今', + '' => '介', + '' => '仄', + '' => '元', + '' => '允', + '' => '內', + '' => '六', + '' => '兮', + '' => '公', + '' => '冗', + '' => '凶', + '' => '分', + '' => '切', + '' => '刈', + '' => '勻', + '' => '勾', + '' => '勿', + '' => '化', + '' => '匹', + '' => '午', + '' => '升', + '' => '卅', + '' => '卞', + '' => '厄', + '' => '友', + '' => '及', + '' => '反', + '' => '壬', + '' => '天', + '' => '夫', + '' => '太', + '' => '夭', + '' => '孔', + '' => '少', + '' => '尤', + '' => '尺', + '' => '屯', + '' => '巴', + '' => '幻', + '' => '廿', + '' => '弔', + '' => '引', + '' => '心', + '' => '戈', + '' => '戶', + '' => '手', + '' => '扎', + '' => '支', + '' => '文', + '' => '斗', + '' => '斤', + '' => '方', + '' => '日', + '' => '曰', + '' => '月', + '' => '木', + '' => '欠', + '' => '止', + '' => '歹', + '' => '毋', + '' => '比', + '' => '毛', + '' => '氏', + '' => '水', + '' => '火', + '' => '爪', + '' => '父', + '' => '爻', + '' => '片', + '' => '牙', + '' => '牛', + '' => '犬', + '' => '王', + '' => '丙', + '@' => '世', + 'A' => '丕', + 'B' => '且', + 'C' => '丘', + 'D' => '主', + 'E' => '乍', + 'F' => '乏', + 'G' => '乎', + 'H' => '以', + 'I' => '付', + 'J' => '仔', + 'K' => '仕', + 'L' => '他', + 'M' => '仗', + 'N' => '代', + 'O' => '令', + 'P' => '仙', + 'Q' => '仞', + 'R' => '充', + 'S' => '兄', + 'T' => '冉', + 'U' => '冊', + 'V' => '冬', + 'W' => '凹', + 'X' => '出', + 'Y' => '凸', + 'Z' => '刊', + '[' => '加', + '\\' => '功', + ']' => '包', + '^' => '匆', + '_' => '北', + '`' => '匝', + 'a' => '仟', + 'b' => '半', + 'c' => '卉', + 'd' => '卡', + 'e' => '占', + 'f' => '卯', + 'g' => '卮', + 'h' => '去', + 'i' => '可', + 'j' => '古', + 'k' => '右', + 'l' => '召', + 'm' => '叮', + 'n' => '叩', + 'o' => '叨', + 'p' => '叼', + 'q' => '司', + 'r' => '叵', + 's' => '叫', + 't' => '另', + 'u' => '只', + 'v' => '史', + 'w' => '叱', + 'x' => '台', + 'y' => '句', + 'z' => '叭', + '{' => '叻', + '|' => '四', + '}' => '囚', + '~' => '外', + '' => '央', + '' => '失', + '' => '奴', + '' => '奶', + '' => '孕', + '' => '它', + '' => '尼', + '' => '巨', + '' => '巧', + '' => '左', + '' => '市', + '' => '布', + '' => '平', + '' => '幼', + '' => '弁', + '' => '弘', + '' => '弗', + '' => '必', + '' => '戊', + '' => '打', + '' => '扔', + '' => '扒', + '' => '扑', + '' => '斥', + '' => '旦', + '' => '朮', + '' => '本', + '' => '未', + '' => '末', + '' => '札', + '' => '正', + '' => '母', + '' => '民', + '' => '氐', + '' => '永', + '' => '汁', + '' => '汀', + '' => '氾', + '' => '犯', + '' => '玄', + '' => '玉', + '' => '瓜', + '' => '瓦', + '' => '甘', + '' => '生', + '' => '用', + '' => '甩', + '' => '田', + '' => '由', + '' => '甲', + '' => '申', + '' => '疋', + '' => '白', + '' => '皮', + '' => '皿', + '' => '目', + '' => '矛', + '' => '矢', + '' => '石', + '' => '示', + '' => '禾', + '' => '穴', + '' => '立', + '' => '丞', + '' => '丟', + '' => '乒', + '' => '乓', + '' => '乩', + '' => '亙', + '' => '交', + '' => '亦', + '' => '亥', + '' => '仿', + '' => '伉', + '' => '伙', + '' => '伊', + '' => '伕', + '' => '伍', + '' => '伐', + '' => '休', + '' => '伏', + '' => '仲', + '' => '件', + '' => '任', + '' => '仰', + '' => '仳', + '' => '份', + '' => '企', + '' => '伋', + '' => '光', + '' => '兇', + '' => '兆', + '' => '先', + '' => '全', + '@' => '共', + 'A' => '再', + 'B' => '冰', + 'C' => '列', + 'D' => '刑', + 'E' => '划', + 'F' => '刎', + 'G' => '刖', + 'H' => '劣', + 'I' => '匈', + 'J' => '匡', + 'K' => '匠', + 'L' => '印', + 'M' => '危', + 'N' => '吉', + 'O' => '吏', + 'P' => '同', + 'Q' => '吊', + 'R' => '吐', + 'S' => '吁', + 'T' => '吋', + 'U' => '各', + 'V' => '向', + 'W' => '名', + 'X' => '合', + 'Y' => '吃', + 'Z' => '后', + '[' => '吆', + '\\' => '吒', + ']' => '因', + '^' => '回', + '_' => '囝', + '`' => '圳', + 'a' => '地', + 'b' => '在', + 'c' => '圭', + 'd' => '圬', + 'e' => '圯', + 'f' => '圩', + 'g' => '夙', + 'h' => '多', + 'i' => '夷', + 'j' => '夸', + 'k' => '妄', + 'l' => '奸', + 'm' => '妃', + 'n' => '好', + 'o' => '她', + 'p' => '如', + 'q' => '妁', + 'r' => '字', + 's' => '存', + 't' => '宇', + 'u' => '守', + 'v' => '宅', + 'w' => '安', + 'x' => '寺', + 'y' => '尖', + 'z' => '屹', + '{' => '州', + '|' => '帆', + '}' => '并', + '~' => '年', + '' => '式', + '' => '弛', + '' => '忙', + '' => '忖', + '' => '戎', + '' => '戌', + '' => '戍', + '' => '成', + '' => '扣', + '' => '扛', + '' => '托', + '' => '收', + '' => '早', + '' => '旨', + '' => '旬', + '' => '旭', + '' => '曲', + '' => '曳', + '' => '有', + '' => '朽', + '' => '朴', + '' => '朱', + '' => '朵', + '' => '次', + '' => '此', + '' => '死', + '' => '氖', + '' => '汝', + '' => '汗', + '' => '汙', + '' => '江', + '' => '池', + '' => '汐', + '' => '汕', + '' => '污', + '' => '汛', + '' => '汍', + '' => '汎', + '' => '灰', + '' => '牟', + '' => '牝', + '' => '百', + '' => '竹', + '' => '米', + '' => '糸', + '' => '缶', + '' => '羊', + '' => '羽', + '' => '老', + '' => '考', + '' => '而', + '' => '耒', + '' => '耳', + '' => '聿', + '' => '肉', + '' => '肋', + '' => '肌', + '' => '臣', + '' => '自', + '' => '至', + '' => '臼', + '' => '舌', + '' => '舛', + '' => '舟', + '' => '艮', + '' => '色', + '' => '艾', + '' => '虫', + '' => '血', + '' => '行', + '' => '衣', + '' => '西', + '' => '阡', + '' => '串', + '' => '亨', + '' => '位', + '' => '住', + '' => '佇', + '' => '佗', + '' => '佞', + '' => '伴', + '' => '佛', + '' => '何', + '' => '估', + '' => '佐', + '' => '佑', + '' => '伽', + '' => '伺', + '' => '伸', + '' => '佃', + '' => '佔', + '' => '似', + '' => '但', + '' => '佣', + '@' => '作', + 'A' => '你', + 'B' => '伯', + 'C' => '低', + 'D' => '伶', + 'E' => '余', + 'F' => '佝', + 'G' => '佈', + 'H' => '佚', + 'I' => '兌', + 'J' => '克', + 'K' => '免', + 'L' => '兵', + 'M' => '冶', + 'N' => '冷', + 'O' => '別', + 'P' => '判', + 'Q' => '利', + 'R' => '刪', + 'S' => '刨', + 'T' => '劫', + 'U' => '助', + 'V' => '努', + 'W' => '劬', + 'X' => '匣', + 'Y' => '即', + 'Z' => '卵', + '[' => '吝', + '\\' => '吭', + ']' => '吞', + '^' => '吾', + '_' => '否', + '`' => '呎', + 'a' => '吧', + 'b' => '呆', + 'c' => '呃', + 'd' => '吳', + 'e' => '呈', + 'f' => '呂', + 'g' => '君', + 'h' => '吩', + 'i' => '告', + 'j' => '吹', + 'k' => '吻', + 'l' => '吸', + 'm' => '吮', + 'n' => '吵', + 'o' => '吶', + 'p' => '吠', + 'q' => '吼', + 'r' => '呀', + 's' => '吱', + 't' => '含', + 'u' => '吟', + 'v' => '听', + 'w' => '囪', + 'x' => '困', + 'y' => '囤', + 'z' => '囫', + '{' => '坊', + '|' => '坑', + '}' => '址', + '~' => '坍', + '' => '均', + '' => '坎', + '' => '圾', + '' => '坐', + '' => '坏', + '' => '圻', + '' => '壯', + '' => '夾', + '' => '妝', + '' => '妒', + '' => '妨', + '' => '妞', + '' => '妣', + '' => '妙', + '' => '妖', + '' => '妍', + '' => '妤', + '' => '妓', + '' => '妊', + '' => '妥', + '' => '孝', + '' => '孜', + '' => '孚', + '' => '孛', + '' => '完', + '' => '宋', + '' => '宏', + '' => '尬', + '' => '局', + '' => '屁', + '' => '尿', + '' => '尾', + '' => '岐', + '' => '岑', + '' => '岔', + '' => '岌', + '' => '巫', + '' => '希', + '' => '序', + '' => '庇', + '' => '床', + '' => '廷', + '' => '弄', + '' => '弟', + '' => '彤', + '' => '形', + '' => '彷', + '' => '役', + '' => '忘', + '' => '忌', + '' => '志', + '' => '忍', + '' => '忱', + '' => '快', + '' => '忸', + '' => '忪', + '' => '戒', + '' => '我', + '' => '抄', + '' => '抗', + '' => '抖', + '' => '技', + '' => '扶', + '' => '抉', + '' => '扭', + '' => '把', + '' => '扼', + '' => '找', + '' => '批', + '' => '扳', + '' => '抒', + '' => '扯', + '' => '折', + '' => '扮', + '' => '投', + '' => '抓', + '' => '抑', + '' => '抆', + '' => '改', + '' => '攻', + '' => '攸', + '' => '旱', + '' => '更', + '' => '束', + '' => '李', + '' => '杏', + '' => '材', + '' => '村', + '' => '杜', + '' => '杖', + '' => '杞', + '' => '杉', + '' => '杆', + '' => '杠', + '@' => '杓', + 'A' => '杗', + 'B' => '步', + 'C' => '每', + 'D' => '求', + 'E' => '汞', + 'F' => '沙', + 'G' => '沁', + 'H' => '沈', + 'I' => '沉', + 'J' => '沅', + 'K' => '沛', + 'L' => '汪', + 'M' => '決', + 'N' => '沐', + 'O' => '汰', + 'P' => '沌', + 'Q' => '汨', + 'R' => '沖', + 'S' => '沒', + 'T' => '汽', + 'U' => '沃', + 'V' => '汲', + 'W' => '汾', + 'X' => '汴', + 'Y' => '沆', + 'Z' => '汶', + '[' => '沍', + '\\' => '沔', + ']' => '沘', + '^' => '沂', + '_' => '灶', + '`' => '灼', + 'a' => '災', + 'b' => '灸', + 'c' => '牢', + 'd' => '牡', + 'e' => '牠', + 'f' => '狄', + 'g' => '狂', + 'h' => '玖', + 'i' => '甬', + 'j' => '甫', + 'k' => '男', + 'l' => '甸', + 'm' => '皂', + 'n' => '盯', + 'o' => '矣', + 'p' => '私', + 'q' => '秀', + 'r' => '禿', + 's' => '究', + 't' => '系', + 'u' => '罕', + 'v' => '肖', + 'w' => '肓', + 'x' => '肝', + 'y' => '肘', + 'z' => '肛', + '{' => '肚', + '|' => '育', + '}' => '良', + '~' => '芒', + '' => '芋', + '' => '芍', + '' => '見', + '' => '角', + '' => '言', + '' => '谷', + '' => '豆', + '' => '豕', + '' => '貝', + '' => '赤', + '' => '走', + '' => '足', + '' => '身', + '' => '車', + '' => '辛', + '' => '辰', + '' => '迂', + '' => '迆', + '' => '迅', + '' => '迄', + '' => '巡', + '' => '邑', + '' => '邢', + '' => '邪', + '' => '邦', + '' => '那', + '' => '酉', + '' => '釆', + '' => '里', + '' => '防', + '' => '阮', + '' => '阱', + '' => '阪', + '' => '阬', + '' => '並', + '' => '乖', + '' => '乳', + '' => '事', + '' => '些', + '' => '亞', + '' => '享', + '' => '京', + '' => '佯', + '' => '依', + '' => '侍', + '' => '佳', + '' => '使', + '' => '佬', + '' => '供', + '' => '例', + '' => '來', + '' => '侃', + '' => '佰', + '' => '併', + '' => '侈', + '' => '佩', + '' => '佻', + '' => '侖', + '' => '佾', + '' => '侏', + '' => '侑', + '' => '佺', + '' => '兔', + '' => '兒', + '' => '兕', + '' => '兩', + '' => '具', + '' => '其', + '' => '典', + '' => '冽', + '' => '函', + '' => '刻', + '' => '券', + '' => '刷', + '' => '刺', + '' => '到', + '' => '刮', + '' => '制', + '' => '剁', + '' => '劾', + '' => '劻', + '' => '卒', + '' => '協', + '' => '卓', + '' => '卑', + '' => '卦', + '' => '卷', + '' => '卸', + '' => '卹', + '' => '取', + '' => '叔', + '' => '受', + '' => '味', + '' => '呵', + '@' => '咖', + 'A' => '呸', + 'B' => '咕', + 'C' => '咀', + 'D' => '呻', + 'E' => '呷', + 'F' => '咄', + 'G' => '咒', + 'H' => '咆', + 'I' => '呼', + 'J' => '咐', + 'K' => '呱', + 'L' => '呶', + 'M' => '和', + 'N' => '咚', + 'O' => '呢', + 'P' => '周', + 'Q' => '咋', + 'R' => '命', + 'S' => '咎', + 'T' => '固', + 'U' => '垃', + 'V' => '坷', + 'W' => '坪', + 'X' => '坩', + 'Y' => '坡', + 'Z' => '坦', + '[' => '坤', + '\\' => '坼', + ']' => '夜', + '^' => '奉', + '_' => '奇', + '`' => '奈', + 'a' => '奄', + 'b' => '奔', + 'c' => '妾', + 'd' => '妻', + 'e' => '委', + 'f' => '妹', + 'g' => '妮', + 'h' => '姑', + 'i' => '姆', + 'j' => '姐', + 'k' => '姍', + 'l' => '始', + 'm' => '姓', + 'n' => '姊', + 'o' => '妯', + 'p' => '妳', + 'q' => '姒', + 'r' => '姅', + 's' => '孟', + 't' => '孤', + 'u' => '季', + 'v' => '宗', + 'w' => '定', + 'x' => '官', + 'y' => '宜', + 'z' => '宙', + '{' => '宛', + '|' => '尚', + '}' => '屈', + '~' => '居', + '' => '屆', + '' => '岷', + '' => '岡', + '' => '岸', + '' => '岩', + '' => '岫', + '' => '岱', + '' => '岳', + '' => '帘', + '' => '帚', + '' => '帖', + '' => '帕', + '' => '帛', + '' => '帑', + '' => '幸', + '' => '庚', + '' => '店', + '' => '府', + '' => '底', + '' => '庖', + '' => '延', + '' => '弦', + '' => '弧', + '' => '弩', + '' => '往', + '' => '征', + '' => '彿', + '' => '彼', + '' => '忝', + '' => '忠', + '' => '忽', + '' => '念', + '' => '忿', + '' => '怏', + '' => '怔', + '' => '怯', + '' => '怵', + '' => '怖', + '' => '怪', + '' => '怕', + '' => '怡', + '' => '性', + '' => '怩', + '' => '怫', + '' => '怛', + '' => '或', + '' => '戕', + '' => '房', + '' => '戾', + '' => '所', + '' => '承', + '' => '拉', + '' => '拌', + '' => '拄', + '' => '抿', + '' => '拂', + '' => '抹', + '' => '拒', + '' => '招', + '' => '披', + '' => '拓', + '' => '拔', + '' => '拋', + '' => '拈', + '' => '抨', + '' => '抽', + '' => '押', + '' => '拐', + '' => '拙', + '' => '拇', + '' => '拍', + '' => '抵', + '' => '拚', + '' => '抱', + '' => '拘', + '' => '拖', + '' => '拗', + '' => '拆', + '' => '抬', + '' => '拎', + '' => '放', + '' => '斧', + '' => '於', + '' => '旺', + '' => '昔', + '' => '易', + '' => '昌', + '' => '昆', + '' => '昂', + '' => '明', + '' => '昀', + '' => '昏', + '' => '昕', + '' => '昊', + '@' => '昇', + 'A' => '服', + 'B' => '朋', + 'C' => '杭', + 'D' => '枋', + 'E' => '枕', + 'F' => '東', + 'G' => '果', + 'H' => '杳', + 'I' => '杷', + 'J' => '枇', + 'K' => '枝', + 'L' => '林', + 'M' => '杯', + 'N' => '杰', + 'O' => '板', + 'P' => '枉', + 'Q' => '松', + 'R' => '析', + 'S' => '杵', + 'T' => '枚', + 'U' => '枓', + 'V' => '杼', + 'W' => '杪', + 'X' => '杲', + 'Y' => '欣', + 'Z' => '武', + '[' => '歧', + '\\' => '歿', + ']' => '氓', + '^' => '氛', + '_' => '泣', + '`' => '注', + 'a' => '泳', + 'b' => '沱', + 'c' => '泌', + 'd' => '泥', + 'e' => '河', + 'f' => '沽', + 'g' => '沾', + 'h' => '沼', + 'i' => '波', + 'j' => '沫', + 'k' => '法', + 'l' => '泓', + 'm' => '沸', + 'n' => '泄', + 'o' => '油', + 'p' => '況', + 'q' => '沮', + 'r' => '泗', + 's' => '泅', + 't' => '泱', + 'u' => '沿', + 'v' => '治', + 'w' => '泡', + 'x' => '泛', + 'y' => '泊', + 'z' => '沬', + '{' => '泯', + '|' => '泜', + '}' => '泖', + '~' => '泠', + '' => '炕', + '' => '炎', + '' => '炒', + '' => '炊', + '' => '炙', + '' => '爬', + '' => '爭', + '' => '爸', + '' => '版', + '' => '牧', + '' => '物', + '' => '狀', + '' => '狎', + '' => '狙', + '' => '狗', + '' => '狐', + '' => '玩', + '' => '玨', + '' => '玟', + '' => '玫', + '' => '玥', + '' => '甽', + '' => '疝', + '' => '疙', + '' => '疚', + '' => '的', + '' => '盂', + '' => '盲', + '' => '直', + '' => '知', + '' => '矽', + '' => '社', + '' => '祀', + '' => '祁', + '' => '秉', + '' => '秈', + '' => '空', + '' => '穹', + '' => '竺', + '' => '糾', + '' => '罔', + '' => '羌', + '' => '羋', + '' => '者', + '' => '肺', + '' => '肥', + '' => '肢', + '' => '肱', + '' => '股', + '' => '肫', + '' => '肩', + '' => '肴', + '' => '肪', + '' => '肯', + '' => '臥', + '' => '臾', + '' => '舍', + '' => '芳', + '' => '芝', + '' => '芙', + '' => '芭', + '' => '芽', + '' => '芟', + '' => '芹', + '' => '花', + '' => '芬', + '' => '芥', + '' => '芯', + '' => '芸', + '' => '芣', + '' => '芰', + '' => '芾', + '' => '芷', + '' => '虎', + '' => '虱', + '' => '初', + '' => '表', + '' => '軋', + '' => '迎', + '' => '返', + '' => '近', + '' => '邵', + '' => '邸', + '' => '邱', + '' => '邶', + '' => '采', + '' => '金', + '' => '長', + '' => '門', + '' => '阜', + '' => '陀', + '' => '阿', + '' => '阻', + '' => '附', + '@' => '陂', + 'A' => '隹', + 'B' => '雨', + 'C' => '青', + 'D' => '非', + 'E' => '亟', + 'F' => '亭', + 'G' => '亮', + 'H' => '信', + 'I' => '侵', + 'J' => '侯', + 'K' => '便', + 'L' => '俠', + 'M' => '俑', + 'N' => '俏', + 'O' => '保', + 'P' => '促', + 'Q' => '侶', + 'R' => '俘', + 'S' => '俟', + 'T' => '俊', + 'U' => '俗', + 'V' => '侮', + 'W' => '俐', + 'X' => '俄', + 'Y' => '係', + 'Z' => '俚', + '[' => '俎', + '\\' => '俞', + ']' => '侷', + '^' => '兗', + '_' => '冒', + '`' => '冑', + 'a' => '冠', + 'b' => '剎', + 'c' => '剃', + 'd' => '削', + 'e' => '前', + 'f' => '剌', + 'g' => '剋', + 'h' => '則', + 'i' => '勇', + 'j' => '勉', + 'k' => '勃', + 'l' => '勁', + 'm' => '匍', + 'n' => '南', + 'o' => '卻', + 'p' => '厚', + 'q' => '叛', + 'r' => '咬', + 's' => '哀', + 't' => '咨', + 'u' => '哎', + 'v' => '哉', + 'w' => '咸', + 'x' => '咦', + 'y' => '咳', + 'z' => '哇', + '{' => '哂', + '|' => '咽', + '}' => '咪', + '~' => '品', + '' => '哄', + '' => '哈', + '' => '咯', + '' => '咫', + '' => '咱', + '' => '咻', + '' => '咩', + '' => '咧', + '' => '咿', + '' => '囿', + '' => '垂', + '' => '型', + '' => '垠', + '' => '垣', + '' => '垢', + '' => '城', + '' => '垮', + '' => '垓', + '' => '奕', + '' => '契', + '' => '奏', + '' => '奎', + '' => '奐', + '' => '姜', + '' => '姘', + '' => '姿', + '' => '姣', + '' => '姨', + '' => '娃', + '' => '姥', + '' => '姪', + '' => '姚', + '' => '姦', + '' => '威', + '' => '姻', + '' => '孩', + '' => '宣', + '' => '宦', + '' => '室', + '' => '客', + '' => '宥', + '' => '封', + '' => '屎', + '' => '屏', + '' => '屍', + '' => '屋', + '' => '峙', + '' => '峒', + '' => '巷', + '' => '帝', + '' => '帥', + '' => '帟', + '' => '幽', + '' => '庠', + '' => '度', + '' => '建', + '' => '弈', + '' => '弭', + '' => '彥', + '' => '很', + '' => '待', + '' => '徊', + '' => '律', + '' => '徇', + '' => '後', + '' => '徉', + '' => '怒', + '' => '思', + '' => '怠', + '' => '急', + '' => '怎', + '' => '怨', + '' => '恍', + '' => '恰', + '' => '恨', + '' => '恢', + '' => '恆', + '' => '恃', + '' => '恬', + '' => '恫', + '' => '恪', + '' => '恤', + '' => '扁', + '' => '拜', + '' => '挖', + '' => '按', + '' => '拼', + '' => '拭', + '' => '持', + '' => '拮', + '' => '拽', + '' => '指', + '' => '拱', + '' => '拷', + '@' => '拯', + 'A' => '括', + 'B' => '拾', + 'C' => '拴', + 'D' => '挑', + 'E' => '挂', + 'F' => '政', + 'G' => '故', + 'H' => '斫', + 'I' => '施', + 'J' => '既', + 'K' => '春', + 'L' => '昭', + 'M' => '映', + 'N' => '昧', + 'O' => '是', + 'P' => '星', + 'Q' => '昨', + 'R' => '昱', + 'S' => '昤', + 'T' => '曷', + 'U' => '柿', + 'V' => '染', + 'W' => '柱', + 'X' => '柔', + 'Y' => '某', + 'Z' => '柬', + '[' => '架', + '\\' => '枯', + ']' => '柵', + '^' => '柩', + '_' => '柯', + '`' => '柄', + 'a' => '柑', + 'b' => '枴', + 'c' => '柚', + 'd' => '查', + 'e' => '枸', + 'f' => '柏', + 'g' => '柞', + 'h' => '柳', + 'i' => '枰', + 'j' => '柙', + 'k' => '柢', + 'l' => '柝', + 'm' => '柒', + 'n' => '歪', + 'o' => '殃', + 'p' => '殆', + 'q' => '段', + 'r' => '毒', + 's' => '毗', + 't' => '氟', + 'u' => '泉', + 'v' => '洋', + 'w' => '洲', + 'x' => '洪', + 'y' => '流', + 'z' => '津', + '{' => '洌', + '|' => '洱', + '}' => '洞', + '~' => '洗', + '' => '活', + '' => '洽', + '' => '派', + '' => '洶', + '' => '洛', + '' => '泵', + '' => '洹', + '' => '洧', + '' => '洸', + '' => '洩', + '' => '洮', + '' => '洵', + '' => '洎', + '' => '洫', + '' => '炫', + '' => '為', + '' => '炳', + '' => '炬', + '' => '炯', + '' => '炭', + '' => '炸', + '' => '炮', + '' => '炤', + '' => '爰', + '' => '牲', + '' => '牯', + '' => '牴', + '' => '狩', + '' => '狠', + '' => '狡', + '' => '玷', + '' => '珊', + '' => '玻', + '' => '玲', + '' => '珍', + '' => '珀', + '' => '玳', + '' => '甚', + '' => '甭', + '' => '畏', + '' => '界', + '' => '畎', + '' => '畋', + '' => '疫', + '' => '疤', + '' => '疥', + '' => '疢', + '' => '疣', + '' => '癸', + '' => '皆', + '' => '皇', + '' => '皈', + '' => '盈', + '' => '盆', + '' => '盃', + '' => '盅', + '' => '省', + '' => '盹', + '' => '相', + '' => '眉', + '' => '看', + '' => '盾', + '' => '盼', + '' => '眇', + '' => '矜', + '' => '砂', + '' => '研', + '' => '砌', + '' => '砍', + '' => '祆', + '' => '祉', + '' => '祈', + '' => '祇', + '' => '禹', + '' => '禺', + '' => '科', + '' => '秒', + '' => '秋', + '' => '穿', + '' => '突', + '' => '竿', + '' => '竽', + '' => '籽', + '' => '紂', + '' => '紅', + '' => '紀', + '' => '紉', + '' => '紇', + '' => '約', + '' => '紆', + '' => '缸', + '' => '美', + '' => '羿', + '' => '耄', + '@' => '耐', + 'A' => '耍', + 'B' => '耑', + 'C' => '耶', + 'D' => '胖', + 'E' => '胥', + 'F' => '胚', + 'G' => '胃', + 'H' => '胄', + 'I' => '背', + 'J' => '胡', + 'K' => '胛', + 'L' => '胎', + 'M' => '胞', + 'N' => '胤', + 'O' => '胝', + 'P' => '致', + 'Q' => '舢', + 'R' => '苧', + 'S' => '范', + 'T' => '茅', + 'U' => '苣', + 'V' => '苛', + 'W' => '苦', + 'X' => '茄', + 'Y' => '若', + 'Z' => '茂', + '[' => '茉', + '\\' => '苒', + ']' => '苗', + '^' => '英', + '_' => '茁', + '`' => '苜', + 'a' => '苔', + 'b' => '苑', + 'c' => '苞', + 'd' => '苓', + 'e' => '苟', + 'f' => '苯', + 'g' => '茆', + 'h' => '虐', + 'i' => '虹', + 'j' => '虻', + 'k' => '虺', + 'l' => '衍', + 'm' => '衫', + 'n' => '要', + 'o' => '觔', + 'p' => '計', + 'q' => '訂', + 'r' => '訃', + 's' => '貞', + 't' => '負', + 'u' => '赴', + 'v' => '赳', + 'w' => '趴', + 'x' => '軍', + 'y' => '軌', + 'z' => '述', + '{' => '迦', + '|' => '迢', + '}' => '迪', + '~' => '迥', + '' => '迭', + '' => '迫', + '' => '迤', + '' => '迨', + '' => '郊', + '' => '郎', + '' => '郁', + '' => '郃', + '' => '酋', + '' => '酊', + '' => '重', + '' => '閂', + '' => '限', + '' => '陋', + '' => '陌', + '' => '降', + '' => '面', + '' => '革', + '' => '韋', + '' => '韭', + '' => '音', + '' => '頁', + '' => '風', + '' => '飛', + '' => '食', + '' => '首', + '' => '香', + '' => '乘', + '' => '亳', + '' => '倌', + '' => '倍', + '' => '倣', + '' => '俯', + '' => '倦', + '' => '倥', + '' => '俸', + '' => '倩', + '' => '倖', + '' => '倆', + '' => '值', + '' => '借', + '' => '倚', + '' => '倒', + '' => '們', + '' => '俺', + '' => '倀', + '' => '倔', + '' => '倨', + '' => '俱', + '' => '倡', + '' => '個', + '' => '候', + '' => '倘', + '' => '俳', + '' => '修', + '' => '倭', + '' => '倪', + '' => '俾', + '' => '倫', + '' => '倉', + '' => '兼', + '' => '冤', + '' => '冥', + '' => '冢', + '' => '凍', + '' => '凌', + '' => '准', + '' => '凋', + '' => '剖', + '' => '剜', + '' => '剔', + '' => '剛', + '' => '剝', + '' => '匪', + '' => '卿', + '' => '原', + '' => '厝', + '' => '叟', + '' => '哨', + '' => '唐', + '' => '唁', + '' => '唷', + '' => '哼', + '' => '哥', + '' => '哲', + '' => '唆', + '' => '哺', + '' => '唔', + '' => '哩', + '' => '哭', + '' => '員', + '' => '唉', + '' => '哮', + '' => '哪', + '@' => '哦', + 'A' => '唧', + 'B' => '唇', + 'C' => '哽', + 'D' => '唏', + 'E' => '圃', + 'F' => '圄', + 'G' => '埂', + 'H' => '埔', + 'I' => '埋', + 'J' => '埃', + 'K' => '堉', + 'L' => '夏', + 'M' => '套', + 'N' => '奘', + 'O' => '奚', + 'P' => '娑', + 'Q' => '娘', + 'R' => '娜', + 'S' => '娟', + 'T' => '娛', + 'U' => '娓', + 'V' => '姬', + 'W' => '娠', + 'X' => '娣', + 'Y' => '娩', + 'Z' => '娥', + '[' => '娌', + '\\' => '娉', + ']' => '孫', + '^' => '屘', + '_' => '宰', + '`' => '害', + 'a' => '家', + 'b' => '宴', + 'c' => '宮', + 'd' => '宵', + 'e' => '容', + 'f' => '宸', + 'g' => '射', + 'h' => '屑', + 'i' => '展', + 'j' => '屐', + 'k' => '峭', + 'l' => '峽', + 'm' => '峻', + 'n' => '峪', + 'o' => '峨', + 'p' => '峰', + 'q' => '島', + 'r' => '崁', + 's' => '峴', + 't' => '差', + 'u' => '席', + 'v' => '師', + 'w' => '庫', + 'x' => '庭', + 'y' => '座', + 'z' => '弱', + '{' => '徒', + '|' => '徑', + '}' => '徐', + '~' => '恙', + '' => '恣', + '' => '恥', + '' => '恐', + '' => '恕', + '' => '恭', + '' => '恩', + '' => '息', + '' => '悄', + '' => '悟', + '' => '悚', + '' => '悍', + '' => '悔', + '' => '悌', + '' => '悅', + '' => '悖', + '' => '扇', + '' => '拳', + '' => '挈', + '' => '拿', + '' => '捎', + '' => '挾', + '' => '振', + '' => '捕', + '' => '捂', + '' => '捆', + '' => '捏', + '' => '捉', + '' => '挺', + '' => '捐', + '' => '挽', + '' => '挪', + '' => '挫', + '' => '挨', + '' => '捍', + '' => '捌', + '' => '效', + '' => '敉', + '' => '料', + '' => '旁', + '' => '旅', + '' => '時', + '' => '晉', + '' => '晏', + '' => '晃', + '' => '晒', + '' => '晌', + '' => '晅', + '' => '晁', + '' => '書', + '' => '朔', + '' => '朕', + '' => '朗', + '' => '校', + '' => '核', + '' => '案', + '' => '框', + '' => '桓', + '' => '根', + '' => '桂', + '' => '桔', + '' => '栩', + '' => '梳', + '' => '栗', + '' => '桌', + '' => '桑', + '' => '栽', + '' => '柴', + '' => '桐', + '' => '桀', + '' => '格', + '' => '桃', + '' => '株', + '' => '桅', + '' => '栓', + '' => '栘', + '' => '桁', + '' => '殊', + '' => '殉', + '' => '殷', + '' => '氣', + '' => '氧', + '' => '氨', + '' => '氦', + '' => '氤', + '' => '泰', + '' => '浪', + '' => '涕', + '' => '消', + '' => '涇', + '' => '浦', + '' => '浸', + '' => '海', + '' => '浙', + '' => '涓', + '@' => '浬', + 'A' => '涉', + 'B' => '浮', + 'C' => '浚', + 'D' => '浴', + 'E' => '浩', + 'F' => '涌', + 'G' => '涊', + 'H' => '浹', + 'I' => '涅', + 'J' => '浥', + 'K' => '涔', + 'L' => '烊', + 'M' => '烘', + 'N' => '烤', + 'O' => '烙', + 'P' => '烈', + 'Q' => '烏', + 'R' => '爹', + 'S' => '特', + 'T' => '狼', + 'U' => '狹', + 'V' => '狽', + 'W' => '狸', + 'X' => '狷', + 'Y' => '玆', + 'Z' => '班', + '[' => '琉', + '\\' => '珮', + ']' => '珠', + '^' => '珪', + '_' => '珞', + '`' => '畔', + 'a' => '畝', + 'b' => '畜', + 'c' => '畚', + 'd' => '留', + 'e' => '疾', + 'f' => '病', + 'g' => '症', + 'h' => '疲', + 'i' => '疳', + 'j' => '疽', + 'k' => '疼', + 'l' => '疹', + 'm' => '痂', + 'n' => '疸', + 'o' => '皋', + 'p' => '皰', + 'q' => '益', + 'r' => '盍', + 's' => '盎', + 't' => '眩', + 'u' => '真', + 'v' => '眠', + 'w' => '眨', + 'x' => '矩', + 'y' => '砰', + 'z' => '砧', + '{' => '砸', + '|' => '砝', + '}' => '破', + '~' => '砷', + '' => '砥', + '' => '砭', + '' => '砠', + '' => '砟', + '' => '砲', + '' => '祕', + '' => '祐', + '' => '祠', + '' => '祟', + '' => '祖', + '' => '神', + '' => '祝', + '' => '祗', + '' => '祚', + '' => '秤', + '' => '秣', + '' => '秧', + '' => '租', + '' => '秦', + '' => '秩', + '' => '秘', + '' => '窄', + '' => '窈', + '' => '站', + '' => '笆', + '' => '笑', + '' => '粉', + '' => '紡', + '' => '紗', + '' => '紋', + '' => '紊', + '' => '素', + '' => '索', + '' => '純', + '' => '紐', + '' => '紕', + '' => '級', + '' => '紜', + '' => '納', + '' => '紙', + '' => '紛', + '' => '缺', + '' => '罟', + '' => '羔', + '' => '翅', + '' => '翁', + '' => '耆', + '' => '耘', + '' => '耕', + '' => '耙', + '' => '耗', + '' => '耽', + '' => '耿', + '' => '胱', + '' => '脂', + '' => '胰', + '' => '脅', + '' => '胭', + '' => '胴', + '' => '脆', + '' => '胸', + '' => '胳', + '' => '脈', + '' => '能', + '' => '脊', + '' => '胼', + '' => '胯', + '' => '臭', + '' => '臬', + '' => '舀', + '' => '舐', + '' => '航', + '' => '舫', + '' => '舨', + '' => '般', + '' => '芻', + '' => '茫', + '' => '荒', + '' => '荔', + '' => '荊', + '' => '茸', + '' => '荐', + '' => '草', + '' => '茵', + '' => '茴', + '' => '荏', + '' => '茲', + '' => '茹', + '' => '茶', + '' => '茗', + '' => '荀', + '' => '茱', + '' => '茨', + '' => '荃', + '@' => '虔', + 'A' => '蚊', + 'B' => '蚪', + 'C' => '蚓', + 'D' => '蚤', + 'E' => '蚩', + 'F' => '蚌', + 'G' => '蚣', + 'H' => '蚜', + 'I' => '衰', + 'J' => '衷', + 'K' => '袁', + 'L' => '袂', + 'M' => '衽', + 'N' => '衹', + 'O' => '記', + 'P' => '訐', + 'Q' => '討', + 'R' => '訌', + 'S' => '訕', + 'T' => '訊', + 'U' => '託', + 'V' => '訓', + 'W' => '訖', + 'X' => '訏', + 'Y' => '訑', + 'Z' => '豈', + '[' => '豺', + '\\' => '豹', + ']' => '財', + '^' => '貢', + '_' => '起', + '`' => '躬', + 'a' => '軒', + 'b' => '軔', + 'c' => '軏', + 'd' => '辱', + 'e' => '送', + 'f' => '逆', + 'g' => '迷', + 'h' => '退', + 'i' => '迺', + 'j' => '迴', + 'k' => '逃', + 'l' => '追', + 'm' => '逅', + 'n' => '迸', + 'o' => '邕', + 'p' => '郡', + 'q' => '郝', + 'r' => '郢', + 's' => '酒', + 't' => '配', + 'u' => '酌', + 'v' => '釘', + 'w' => '針', + 'x' => '釗', + 'y' => '釜', + 'z' => '釙', + '{' => '閃', + '|' => '院', + '}' => '陣', + '~' => '陡', + '' => '陛', + '' => '陝', + '' => '除', + '' => '陘', + '' => '陞', + '' => '隻', + '' => '飢', + '' => '馬', + '' => '骨', + '' => '高', + '' => '鬥', + '' => '鬲', + '' => '鬼', + '' => '乾', + '' => '偺', + '' => '偽', + '' => '停', + '' => '假', + '' => '偃', + '' => '偌', + '' => '做', + '' => '偉', + '' => '健', + '' => '偶', + '' => '偎', + '' => '偕', + '' => '偵', + '' => '側', + '' => '偷', + '' => '偏', + '' => '倏', + '' => '偯', + '' => '偭', + '' => '兜', + '' => '冕', + '' => '凰', + '' => '剪', + '' => '副', + '' => '勒', + '' => '務', + '' => '勘', + '' => '動', + '' => '匐', + '' => '匏', + '' => '匙', + '' => '匿', + '' => '區', + '' => '匾', + '' => '參', + '' => '曼', + '' => '商', + '' => '啪', + '' => '啦', + '' => '啄', + '' => '啞', + '' => '啡', + '' => '啃', + '' => '啊', + '' => '唱', + '' => '啖', + '' => '問', + '' => '啕', + '' => '唯', + '' => '啤', + '' => '唸', + '' => '售', + '' => '啜', + '' => '唬', + '' => '啣', + '' => '唳', + '' => '啁', + '' => '啗', + '' => '圈', + '' => '國', + '' => '圉', + '' => '域', + '' => '堅', + '' => '堊', + '' => '堆', + '' => '埠', + '' => '埤', + '' => '基', + '' => '堂', + '' => '堵', + '' => '執', + '' => '培', + '' => '夠', + '' => '奢', + '' => '娶', + '' => '婁', + '' => '婉', + '' => '婦', + '' => '婪', + '' => '婀', + '@' => '娼', + 'A' => '婢', + 'B' => '婚', + 'C' => '婆', + 'D' => '婊', + 'E' => '孰', + 'F' => '寇', + 'G' => '寅', + 'H' => '寄', + 'I' => '寂', + 'J' => '宿', + 'K' => '密', + 'L' => '尉', + 'M' => '專', + 'N' => '將', + 'O' => '屠', + 'P' => '屜', + 'Q' => '屝', + 'R' => '崇', + 'S' => '崆', + 'T' => '崎', + 'U' => '崛', + 'V' => '崖', + 'W' => '崢', + 'X' => '崑', + 'Y' => '崩', + 'Z' => '崔', + '[' => '崙', + '\\' => '崤', + ']' => '崧', + '^' => '崗', + '_' => '巢', + '`' => '常', + 'a' => '帶', + 'b' => '帳', + 'c' => '帷', + 'd' => '康', + 'e' => '庸', + 'f' => '庶', + 'g' => '庵', + 'h' => '庾', + 'i' => '張', + 'j' => '強', + 'k' => '彗', + 'l' => '彬', + 'm' => '彩', + 'n' => '彫', + 'o' => '得', + 'p' => '徙', + 'q' => '從', + 'r' => '徘', + 's' => '御', + 't' => '徠', + 'u' => '徜', + 'v' => '恿', + 'w' => '患', + 'x' => '悉', + 'y' => '悠', + 'z' => '您', + '{' => '惋', + '|' => '悴', + '}' => '惦', + '~' => '悽', + '' => '情', + '' => '悻', + '' => '悵', + '' => '惜', + '' => '悼', + '' => '惘', + '' => '惕', + '' => '惆', + '' => '惟', + '' => '悸', + '' => '惚', + '' => '惇', + '' => '戚', + '' => '戛', + '' => '扈', + '' => '掠', + '' => '控', + '' => '捲', + '' => '掖', + '' => '探', + '' => '接', + '' => '捷', + '' => '捧', + '' => '掘', + '' => '措', + '' => '捱', + '' => '掩', + '' => '掉', + '' => '掃', + '' => '掛', + '' => '捫', + '' => '推', + '' => '掄', + '' => '授', + '' => '掙', + '' => '採', + '' => '掬', + '' => '排', + '' => '掏', + '' => '掀', + '' => '捻', + '' => '捩', + '' => '捨', + '' => '捺', + '' => '敝', + '' => '敖', + '' => '救', + '' => '教', + '' => '敗', + '' => '啟', + '' => '敏', + '' => '敘', + '' => '敕', + '' => '敔', + '' => '斜', + '' => '斛', + '' => '斬', + '' => '族', + '' => '旋', + '' => '旌', + '' => '旎', + '' => '晝', + '' => '晚', + '' => '晤', + '' => '晨', + '' => '晦', + '' => '晞', + '' => '曹', + '' => '勗', + '' => '望', + '' => '梁', + '' => '梯', + '' => '梢', + '' => '梓', + '' => '梵', + '' => '桿', + '' => '桶', + '' => '梱', + '' => '梧', + '' => '梗', + '' => '械', + '' => '梃', + '' => '棄', + '' => '梭', + '' => '梆', + '' => '梅', + '' => '梔', + '' => '條', + '' => '梨', + '' => '梟', + '' => '梡', + '' => '梂', + '' => '欲', + '' => '殺', + '@' => '毫', + 'A' => '毬', + 'B' => '氫', + 'C' => '涎', + 'D' => '涼', + 'E' => '淳', + 'F' => '淙', + 'G' => '液', + 'H' => '淡', + 'I' => '淌', + 'J' => '淤', + 'K' => '添', + 'L' => '淺', + 'M' => '清', + 'N' => '淇', + 'O' => '淋', + 'P' => '涯', + 'Q' => '淑', + 'R' => '涮', + 'S' => '淞', + 'T' => '淹', + 'U' => '涸', + 'V' => '混', + 'W' => '淵', + 'X' => '淅', + 'Y' => '淒', + 'Z' => '渚', + '[' => '涵', + '\\' => '淚', + ']' => '淫', + '^' => '淘', + '_' => '淪', + '`' => '深', + 'a' => '淮', + 'b' => '淨', + 'c' => '淆', + 'd' => '淄', + 'e' => '涪', + 'f' => '淬', + 'g' => '涿', + 'h' => '淦', + 'i' => '烹', + 'j' => '焉', + 'k' => '焊', + 'l' => '烽', + 'm' => '烯', + 'n' => '爽', + 'o' => '牽', + 'p' => '犁', + 'q' => '猜', + 'r' => '猛', + 's' => '猖', + 't' => '猓', + 'u' => '猙', + 'v' => '率', + 'w' => '琅', + 'x' => '琊', + 'y' => '球', + 'z' => '理', + '{' => '現', + '|' => '琍', + '}' => '瓠', + '~' => '瓶', + '' => '瓷', + '' => '甜', + '' => '產', + '' => '略', + '' => '畦', + '' => '畢', + '' => '異', + '' => '疏', + '' => '痔', + '' => '痕', + '' => '疵', + '' => '痊', + '' => '痍', + '' => '皎', + '' => '盔', + '' => '盒', + '' => '盛', + '' => '眷', + '' => '眾', + '' => '眼', + '' => '眶', + '' => '眸', + '' => '眺', + '' => '硫', + '' => '硃', + '' => '硎', + '' => '祥', + '' => '票', + '' => '祭', + '' => '移', + '' => '窒', + '' => '窕', + '' => '笠', + '' => '笨', + '' => '笛', + '' => '第', + '' => '符', + '' => '笙', + '' => '笞', + '' => '笮', + '' => '粒', + '' => '粗', + '' => '粕', + '' => '絆', + '' => '絃', + '' => '統', + '' => '紮', + '' => '紹', + '' => '紼', + '' => '絀', + '' => '細', + '' => '紳', + '' => '組', + '' => '累', + '' => '終', + '' => '紲', + '' => '紱', + '' => '缽', + '' => '羞', + '' => '羚', + '' => '翌', + '' => '翎', + '' => '習', + '' => '耜', + '' => '聊', + '' => '聆', + '' => '脯', + '' => '脖', + '' => '脣', + '' => '脫', + '' => '脩', + '' => '脰', + '' => '脤', + '' => '舂', + '' => '舵', + '' => '舷', + '' => '舶', + '' => '船', + '' => '莎', + '' => '莞', + '' => '莘', + '' => '荸', + '' => '莢', + '' => '莖', + '' => '莽', + '' => '莫', + '' => '莒', + '' => '莊', + '' => '莓', + '' => '莉', + '' => '莠', + '' => '荷', + '' => '荻', + '' => '荼', + '@' => '莆', + 'A' => '莧', + 'B' => '處', + 'C' => '彪', + 'D' => '蛇', + 'E' => '蛀', + 'F' => '蚶', + 'G' => '蛄', + 'H' => '蚵', + 'I' => '蛆', + 'J' => '蛋', + 'K' => '蚱', + 'L' => '蚯', + 'M' => '蛉', + 'N' => '術', + 'O' => '袞', + 'P' => '袈', + 'Q' => '被', + 'R' => '袒', + 'S' => '袖', + 'T' => '袍', + 'U' => '袋', + 'V' => '覓', + 'W' => '規', + 'X' => '訪', + 'Y' => '訝', + 'Z' => '訣', + '[' => '訥', + '\\' => '許', + ']' => '設', + '^' => '訟', + '_' => '訛', + '`' => '訢', + 'a' => '豉', + 'b' => '豚', + 'c' => '販', + 'd' => '責', + 'e' => '貫', + 'f' => '貨', + 'g' => '貪', + 'h' => '貧', + 'i' => '赧', + 'j' => '赦', + 'k' => '趾', + 'l' => '趺', + 'm' => '軛', + 'n' => '軟', + 'o' => '這', + 'p' => '逍', + 'q' => '通', + 'r' => '逗', + 's' => '連', + 't' => '速', + 'u' => '逝', + 'v' => '逐', + 'w' => '逕', + 'x' => '逞', + 'y' => '造', + 'z' => '透', + '{' => '逢', + '|' => '逖', + '}' => '逛', + '~' => '途', + '' => '部', + '' => '郭', + '' => '都', + '' => '酗', + '' => '野', + '' => '釵', + '' => '釦', + '' => '釣', + '' => '釧', + '' => '釭', + '' => '釩', + '' => '閉', + '' => '陪', + '' => '陵', + '' => '陳', + '' => '陸', + '' => '陰', + '' => '陴', + '' => '陶', + '' => '陷', + '' => '陬', + '' => '雀', + '' => '雪', + '' => '雩', + '' => '章', + '' => '竟', + '' => '頂', + '' => '頃', + '' => '魚', + '' => '鳥', + '' => '鹵', + '' => '鹿', + '' => '麥', + '' => '麻', + '' => '傢', + '' => '傍', + '' => '傅', + '' => '備', + '' => '傑', + '' => '傀', + '' => '傖', + '' => '傘', + '' => '傚', + '' => '最', + '' => '凱', + '' => '割', + '' => '剴', + '' => '創', + '' => '剩', + '' => '勞', + '' => '勝', + '' => '勛', + '' => '博', + '' => '厥', + '' => '啻', + '' => '喀', + '' => '喧', + '' => '啼', + '' => '喊', + '' => '喝', + '' => '喘', + '' => '喂', + '' => '喜', + '' => '喪', + '' => '喔', + '' => '喇', + '' => '喋', + '' => '喃', + '' => '喳', + '' => '單', + '' => '喟', + '' => '唾', + '' => '喲', + '' => '喚', + '' => '喻', + '' => '喬', + '' => '喱', + '' => '啾', + '' => '喉', + '' => '喫', + '' => '喙', + '' => '圍', + '' => '堯', + '' => '堪', + '' => '場', + '' => '堤', + '' => '堰', + '' => '報', + '' => '堡', + '' => '堝', + '' => '堠', + '' => '壹', + '' => '壺', + '' => '奠', + '@' => '婷', + 'A' => '媚', + 'B' => '婿', + 'C' => '媒', + 'D' => '媛', + 'E' => '媧', + 'F' => '孳', + 'G' => '孱', + 'H' => '寒', + 'I' => '富', + 'J' => '寓', + 'K' => '寐', + 'L' => '尊', + 'M' => '尋', + 'N' => '就', + 'O' => '嵌', + 'P' => '嵐', + 'Q' => '崴', + 'R' => '嵇', + 'S' => '巽', + 'T' => '幅', + 'U' => '帽', + 'V' => '幀', + 'W' => '幃', + 'X' => '幾', + 'Y' => '廊', + 'Z' => '廁', + '[' => '廂', + '\\' => '廄', + ']' => '弼', + '^' => '彭', + '_' => '復', + '`' => '循', + 'a' => '徨', + 'b' => '惑', + 'c' => '惡', + 'd' => '悲', + 'e' => '悶', + 'f' => '惠', + 'g' => '愜', + 'h' => '愣', + 'i' => '惺', + 'j' => '愕', + 'k' => '惰', + 'l' => '惻', + 'm' => '惴', + 'n' => '慨', + 'o' => '惱', + 'p' => '愎', + 'q' => '惶', + 'r' => '愉', + 's' => '愀', + 't' => '愒', + 'u' => '戟', + 'v' => '扉', + 'w' => '掣', + 'x' => '掌', + 'y' => '描', + 'z' => '揀', + '{' => '揩', + '|' => '揉', + '}' => '揆', + '~' => '揍', + '' => '插', + '' => '揣', + '' => '提', + '' => '握', + '' => '揖', + '' => '揭', + '' => '揮', + '' => '捶', + '' => '援', + '' => '揪', + '' => '換', + '' => '摒', + '' => '揚', + '' => '揹', + '' => '敞', + '' => '敦', + '' => '敢', + '' => '散', + '' => '斑', + '' => '斐', + '' => '斯', + '' => '普', + '' => '晰', + '' => '晴', + '' => '晶', + '' => '景', + '' => '暑', + '' => '智', + '' => '晾', + '' => '晷', + '' => '曾', + '' => '替', + '' => '期', + '' => '朝', + '' => '棺', + '' => '棕', + '' => '棠', + '' => '棘', + '' => '棗', + '' => '椅', + '' => '棟', + '' => '棵', + '' => '森', + '' => '棧', + '' => '棹', + '' => '棒', + '' => '棲', + '' => '棣', + '' => '棋', + '' => '棍', + '' => '植', + '' => '椒', + '' => '椎', + '' => '棉', + '' => '棚', + '' => '楮', + '' => '棻', + '' => '款', + '' => '欺', + '' => '欽', + '' => '殘', + '' => '殖', + '' => '殼', + '' => '毯', + '' => '氮', + '' => '氯', + '' => '氬', + '' => '港', + '' => '游', + '' => '湔', + '' => '渡', + '' => '渲', + '' => '湧', + '' => '湊', + '' => '渠', + '' => '渥', + '' => '渣', + '' => '減', + '' => '湛', + '' => '湘', + '' => '渤', + '' => '湖', + '' => '湮', + '' => '渭', + '' => '渦', + '' => '湯', + '' => '渴', + '' => '湍', + '' => '渺', + '' => '測', + '' => '湃', + '' => '渝', + '' => '渾', + '' => '滋', + '@' => '溉', + 'A' => '渙', + 'B' => '湎', + 'C' => '湣', + 'D' => '湄', + 'E' => '湲', + 'F' => '湩', + 'G' => '湟', + 'H' => '焙', + 'I' => '焚', + 'J' => '焦', + 'K' => '焰', + 'L' => '無', + 'M' => '然', + 'N' => '煮', + 'O' => '焜', + 'P' => '牌', + 'Q' => '犄', + 'R' => '犀', + 'S' => '猶', + 'T' => '猥', + 'U' => '猴', + 'V' => '猩', + 'W' => '琺', + 'X' => '琪', + 'Y' => '琳', + 'Z' => '琢', + '[' => '琥', + '\\' => '琵', + ']' => '琶', + '^' => '琴', + '_' => '琯', + '`' => '琛', + 'a' => '琦', + 'b' => '琨', + 'c' => '甥', + 'd' => '甦', + 'e' => '畫', + 'f' => '番', + 'g' => '痢', + 'h' => '痛', + 'i' => '痣', + 'j' => '痙', + 'k' => '痘', + 'l' => '痞', + 'm' => '痠', + 'n' => '登', + 'o' => '發', + 'p' => '皖', + 'q' => '皓', + 'r' => '皴', + 's' => '盜', + 't' => '睏', + 'u' => '短', + 'v' => '硝', + 'w' => '硬', + 'x' => '硯', + 'y' => '稍', + 'z' => '稈', + '{' => '程', + '|' => '稅', + '}' => '稀', + '~' => '窘', + '' => '窗', + '' => '窖', + '' => '童', + '' => '竣', + '' => '等', + '' => '策', + '' => '筆', + '' => '筐', + '' => '筒', + '' => '答', + '' => '筍', + '' => '筋', + '' => '筏', + '' => '筑', + '' => '粟', + '' => '粥', + '' => '絞', + '' => '結', + '' => '絨', + '' => '絕', + '' => '紫', + '' => '絮', + '' => '絲', + '' => '絡', + '' => '給', + '' => '絢', + '' => '絰', + '' => '絳', + '' => '善', + '' => '翔', + '' => '翕', + '' => '耋', + '' => '聒', + '' => '肅', + '' => '腕', + '' => '腔', + '' => '腋', + '' => '腑', + '' => '腎', + '' => '脹', + '' => '腆', + '' => '脾', + '' => '腌', + '' => '腓', + '' => '腴', + '' => '舒', + '' => '舜', + '' => '菩', + '' => '萃', + '' => '菸', + '' => '萍', + '' => '菠', + '' => '菅', + '' => '萋', + '' => '菁', + '' => '華', + '' => '菱', + '' => '菴', + '' => '著', + '' => '萊', + '' => '菰', + '' => '萌', + '' => '菌', + '' => '菽', + '' => '菲', + '' => '菊', + '' => '萸', + '' => '萎', + '' => '萄', + '' => '菜', + '' => '萇', + '' => '菔', + '' => '菟', + '' => '虛', + '' => '蛟', + '' => '蛙', + '' => '蛭', + '' => '蛔', + '' => '蛛', + '' => '蛤', + '' => '蛐', + '' => '蛞', + '' => '街', + '' => '裁', + '' => '裂', + '' => '袱', + '' => '覃', + '' => '視', + '' => '註', + '' => '詠', + '' => '評', + '' => '詞', + '' => '証', + '' => '詁', + '@' => '詔', + 'A' => '詛', + 'B' => '詐', + 'C' => '詆', + 'D' => '訴', + 'E' => '診', + 'F' => '訶', + 'G' => '詖', + 'H' => '象', + 'I' => '貂', + 'J' => '貯', + 'K' => '貼', + 'L' => '貳', + 'M' => '貽', + 'N' => '賁', + 'O' => '費', + 'P' => '賀', + 'Q' => '貴', + 'R' => '買', + 'S' => '貶', + 'T' => '貿', + 'U' => '貸', + 'V' => '越', + 'W' => '超', + 'X' => '趁', + 'Y' => '跎', + 'Z' => '距', + '[' => '跋', + '\\' => '跚', + ']' => '跑', + '^' => '跌', + '_' => '跛', + '`' => '跆', + 'a' => '軻', + 'b' => '軸', + 'c' => '軼', + 'd' => '辜', + 'e' => '逮', + 'f' => '逵', + 'g' => '週', + 'h' => '逸', + 'i' => '進', + 'j' => '逶', + 'k' => '鄂', + 'l' => '郵', + 'm' => '鄉', + 'n' => '郾', + 'o' => '酣', + 'p' => '酥', + 'q' => '量', + 'r' => '鈔', + 's' => '鈕', + 't' => '鈣', + 'u' => '鈉', + 'v' => '鈞', + 'w' => '鈍', + 'x' => '鈐', + 'y' => '鈇', + 'z' => '鈑', + '{' => '閔', + '|' => '閏', + '}' => '開', + '~' => '閑', + '' => '間', + '' => '閒', + '' => '閎', + '' => '隊', + '' => '階', + '' => '隋', + '' => '陽', + '' => '隅', + '' => '隆', + '' => '隍', + '' => '陲', + '' => '隄', + '' => '雁', + '' => '雅', + '' => '雄', + '' => '集', + '' => '雇', + '' => '雯', + '' => '雲', + '' => '韌', + '' => '項', + '' => '順', + '' => '須', + '' => '飧', + '' => '飪', + '' => '飯', + '' => '飩', + '' => '飲', + '' => '飭', + '' => '馮', + '' => '馭', + '' => '黃', + '' => '黍', + '' => '黑', + '' => '亂', + '' => '傭', + '' => '債', + '' => '傲', + '' => '傳', + '' => '僅', + '' => '傾', + '' => '催', + '' => '傷', + '' => '傻', + '' => '傯', + '' => '僇', + '' => '剿', + '' => '剷', + '' => '剽', + '' => '募', + '' => '勦', + '' => '勤', + '' => '勢', + '' => '勣', + '' => '匯', + '' => '嗟', + '' => '嗨', + '' => '嗓', + '' => '嗦', + '' => '嗎', + '' => '嗜', + '' => '嗇', + '' => '嗑', + '' => '嗣', + '' => '嗤', + '' => '嗯', + '' => '嗚', + '' => '嗡', + '' => '嗅', + '' => '嗆', + '' => '嗥', + '' => '嗉', + '' => '園', + '' => '圓', + '' => '塞', + '' => '塑', + '' => '塘', + '' => '塗', + '' => '塚', + '' => '塔', + '' => '填', + '' => '塌', + '' => '塭', + '' => '塊', + '' => '塢', + '' => '塒', + '' => '塋', + '' => '奧', + '' => '嫁', + '' => '嫉', + '' => '嫌', + '' => '媾', + '' => '媽', + '' => '媼', + '@' => '媳', + 'A' => '嫂', + 'B' => '媲', + 'C' => '嵩', + 'D' => '嵯', + 'E' => '幌', + 'F' => '幹', + 'G' => '廉', + 'H' => '廈', + 'I' => '弒', + 'J' => '彙', + 'K' => '徬', + 'L' => '微', + 'M' => '愚', + 'N' => '意', + 'O' => '慈', + 'P' => '感', + 'Q' => '想', + 'R' => '愛', + 'S' => '惹', + 'T' => '愁', + 'U' => '愈', + 'V' => '慎', + 'W' => '慌', + 'X' => '慄', + 'Y' => '慍', + 'Z' => '愾', + '[' => '愴', + '\\' => '愧', + ']' => '愍', + '^' => '愆', + '_' => '愷', + '`' => '戡', + 'a' => '戢', + 'b' => '搓', + 'c' => '搾', + 'd' => '搞', + 'e' => '搪', + 'f' => '搭', + 'g' => '搽', + 'h' => '搬', + 'i' => '搏', + 'j' => '搜', + 'k' => '搔', + 'l' => '損', + 'm' => '搶', + 'n' => '搖', + 'o' => '搗', + 'p' => '搆', + 'q' => '敬', + 'r' => '斟', + 's' => '新', + 't' => '暗', + 'u' => '暉', + 'v' => '暇', + 'w' => '暈', + 'x' => '暖', + 'y' => '暄', + 'z' => '暘', + '{' => '暍', + '|' => '會', + '}' => '榔', + '~' => '業', + '' => '楚', + '' => '楷', + '' => '楠', + '' => '楔', + '' => '極', + '' => '椰', + '' => '概', + '' => '楊', + '' => '楨', + '' => '楫', + '' => '楞', + '' => '楓', + '' => '楹', + '' => '榆', + '' => '楝', + '' => '楣', + '' => '楛', + '' => '歇', + '' => '歲', + '' => '毀', + '' => '殿', + '' => '毓', + '' => '毽', + '' => '溢', + '' => '溯', + '' => '滓', + '' => '溶', + '' => '滂', + '' => '源', + '' => '溝', + '' => '滇', + '' => '滅', + '' => '溥', + '' => '溘', + '' => '溼', + '' => '溺', + '' => '溫', + '' => '滑', + '' => '準', + '' => '溜', + '' => '滄', + '' => '滔', + '' => '溪', + '' => '溧', + '' => '溴', + '' => '煎', + '' => '煙', + '' => '煩', + '' => '煤', + '' => '煉', + '' => '照', + '' => '煜', + '' => '煬', + '' => '煦', + '' => '煌', + '' => '煥', + '' => '煞', + '' => '煆', + '' => '煨', + '' => '煖', + '' => '爺', + '' => '牒', + '' => '猷', + '' => '獅', + '' => '猿', + '' => '猾', + '' => '瑯', + '' => '瑚', + '' => '瑕', + '' => '瑟', + '' => '瑞', + '' => '瑁', + '' => '琿', + '' => '瑙', + '' => '瑛', + '' => '瑜', + '' => '當', + '' => '畸', + '' => '瘀', + '' => '痰', + '' => '瘁', + '' => '痲', + '' => '痱', + '' => '痺', + '' => '痿', + '' => '痴', + '' => '痳', + '' => '盞', + '' => '盟', + '' => '睛', + '' => '睫', + '' => '睦', + '' => '睞', + '' => '督', + '@' => '睹', + 'A' => '睪', + 'B' => '睬', + 'C' => '睜', + 'D' => '睥', + 'E' => '睨', + 'F' => '睢', + 'G' => '矮', + 'H' => '碎', + 'I' => '碰', + 'J' => '碗', + 'K' => '碘', + 'L' => '碌', + 'M' => '碉', + 'N' => '硼', + 'O' => '碑', + 'P' => '碓', + 'Q' => '硿', + 'R' => '祺', + 'S' => '祿', + 'T' => '禁', + 'U' => '萬', + 'V' => '禽', + 'W' => '稜', + 'X' => '稚', + 'Y' => '稠', + 'Z' => '稔', + '[' => '稟', + '\\' => '稞', + ']' => '窟', + '^' => '窠', + '_' => '筷', + '`' => '節', + 'a' => '筠', + 'b' => '筮', + 'c' => '筧', + 'd' => '粱', + 'e' => '粳', + 'f' => '粵', + 'g' => '經', + 'h' => '絹', + 'i' => '綑', + 'j' => '綁', + 'k' => '綏', + 'l' => '絛', + 'm' => '置', + 'n' => '罩', + 'o' => '罪', + 'p' => '署', + 'q' => '義', + 'r' => '羨', + 's' => '群', + 't' => '聖', + 'u' => '聘', + 'v' => '肆', + 'w' => '肄', + 'x' => '腱', + 'y' => '腰', + 'z' => '腸', + '{' => '腥', + '|' => '腮', + '}' => '腳', + '~' => '腫', + '' => '腹', + '' => '腺', + '' => '腦', + '' => '舅', + '' => '艇', + '' => '蒂', + '' => '葷', + '' => '落', + '' => '萱', + '' => '葵', + '' => '葦', + '' => '葫', + '' => '葉', + '' => '葬', + '' => '葛', + '' => '萼', + '' => '萵', + '' => '葡', + '' => '董', + '' => '葩', + '' => '葭', + '' => '葆', + '' => '虞', + '' => '虜', + '' => '號', + '' => '蛹', + '' => '蜓', + '' => '蜈', + '' => '蜇', + '' => '蜀', + '' => '蛾', + '' => '蛻', + '' => '蜂', + '' => '蜃', + '' => '蜆', + '' => '蜊', + '' => '衙', + '' => '裟', + '' => '裔', + '' => '裙', + '' => '補', + '' => '裘', + '' => '裝', + '' => '裡', + '' => '裊', + '' => '裕', + '' => '裒', + '' => '覜', + '' => '解', + '' => '詫', + '' => '該', + '' => '詳', + '' => '試', + '' => '詩', + '' => '詰', + '' => '誇', + '' => '詼', + '' => '詣', + '' => '誠', + '' => '話', + '' => '誅', + '' => '詭', + '' => '詢', + '' => '詮', + '' => '詬', + '' => '詹', + '' => '詻', + '' => '訾', + '' => '詨', + '' => '豢', + '' => '貊', + '' => '貉', + '' => '賊', + '' => '資', + '' => '賈', + '' => '賄', + '' => '貲', + '' => '賃', + '' => '賂', + '' => '賅', + '' => '跡', + '' => '跟', + '' => '跨', + '' => '路', + '' => '跳', + '' => '跺', + '' => '跪', + '' => '跤', + '' => '跦', + '' => '躲', + '' => '較', + '' => '載', + '' => '軾', + '' => '輊', + '@' => '辟', + 'A' => '農', + 'B' => '運', + 'C' => '遊', + 'D' => '道', + 'E' => '遂', + 'F' => '達', + 'G' => '逼', + 'H' => '違', + 'I' => '遐', + 'J' => '遇', + 'K' => '遏', + 'L' => '過', + 'M' => '遍', + 'N' => '遑', + 'O' => '逾', + 'P' => '遁', + 'Q' => '鄒', + 'R' => '鄗', + 'S' => '酬', + 'T' => '酪', + 'U' => '酩', + 'V' => '釉', + 'W' => '鈷', + 'X' => '鉗', + 'Y' => '鈸', + 'Z' => '鈽', + '[' => '鉀', + '\\' => '鈾', + ']' => '鉛', + '^' => '鉋', + '_' => '鉤', + '`' => '鉑', + 'a' => '鈴', + 'b' => '鉉', + 'c' => '鉍', + 'd' => '鉅', + 'e' => '鈹', + 'f' => '鈿', + 'g' => '鉚', + 'h' => '閘', + 'i' => '隘', + 'j' => '隔', + 'k' => '隕', + 'l' => '雍', + 'm' => '雋', + 'n' => '雉', + 'o' => '雊', + 'p' => '雷', + 'q' => '電', + 'r' => '雹', + 's' => '零', + 't' => '靖', + 'u' => '靴', + 'v' => '靶', + 'w' => '預', + 'x' => '頑', + 'y' => '頓', + 'z' => '頊', + '{' => '頒', + '|' => '頌', + '}' => '飼', + '~' => '飴', + '' => '飽', + '' => '飾', + '' => '馳', + '' => '馱', + '' => '馴', + '' => '髡', + '' => '鳩', + '' => '麂', + '' => '鼎', + '' => '鼓', + '' => '鼠', + '' => '僧', + '' => '僮', + '' => '僥', + '' => '僖', + '' => '僭', + '' => '僚', + '' => '僕', + '' => '像', + '' => '僑', + '' => '僱', + '' => '僎', + '' => '僩', + '' => '兢', + '' => '凳', + '' => '劃', + '' => '劂', + '' => '匱', + '' => '厭', + '' => '嗾', + '' => '嘀', + '' => '嘛', + '' => '嘗', + '' => '嗽', + '' => '嘔', + '' => '嘆', + '' => '嘉', + '' => '嘍', + '' => '嘎', + '' => '嗷', + '' => '嘖', + '' => '嘟', + '' => '嘈', + '' => '嘐', + '' => '嗶', + '' => '團', + '' => '圖', + '' => '塵', + '' => '塾', + '' => '境', + '' => '墓', + '' => '墊', + '' => '塹', + '' => '墅', + '' => '塽', + '' => '壽', + '' => '夥', + '' => '夢', + '' => '夤', + '' => '奪', + '' => '奩', + '' => '嫡', + '' => '嫦', + '' => '嫩', + '' => '嫗', + '' => '嫖', + '' => '嫘', + '' => '嫣', + '' => '孵', + '' => '寞', + '' => '寧', + '' => '寡', + '' => '寥', + '' => '實', + '' => '寨', + '' => '寢', + '' => '寤', + '' => '察', + '' => '對', + '' => '屢', + '' => '嶄', + '' => '嶇', + '' => '幛', + '' => '幣', + '' => '幕', + '' => '幗', + '' => '幔', + '' => '廓', + '' => '廖', + '' => '弊', + '' => '彆', + '' => '彰', + '' => '徹', + '' => '慇', + '@' => '愿', + 'A' => '態', + 'B' => '慷', + 'C' => '慢', + 'D' => '慣', + 'E' => '慟', + 'F' => '慚', + 'G' => '慘', + 'H' => '慵', + 'I' => '截', + 'J' => '撇', + 'K' => '摘', + 'L' => '摔', + 'M' => '撤', + 'N' => '摸', + 'O' => '摟', + 'P' => '摺', + 'Q' => '摑', + 'R' => '摧', + 'S' => '搴', + 'T' => '摭', + 'U' => '摻', + 'V' => '敲', + 'W' => '斡', + 'X' => '旗', + 'Y' => '旖', + 'Z' => '暢', + '[' => '暨', + '\\' => '暝', + ']' => '榜', + '^' => '榨', + '_' => '榕', + '`' => '槁', + 'a' => '榮', + 'b' => '槓', + 'c' => '構', + 'd' => '榛', + 'e' => '榷', + 'f' => '榻', + 'g' => '榫', + 'h' => '榴', + 'i' => '槐', + 'j' => '槍', + 'k' => '榭', + 'l' => '槌', + 'm' => '榦', + 'n' => '槃', + 'o' => '榣', + 'p' => '歉', + 'q' => '歌', + 'r' => '氳', + 's' => '漳', + 't' => '演', + 'u' => '滾', + 'v' => '漓', + 'w' => '滴', + 'x' => '漩', + 'y' => '漾', + 'z' => '漠', + '{' => '漬', + '|' => '漏', + '}' => '漂', + '~' => '漢', + '' => '滿', + '' => '滯', + '' => '漆', + '' => '漱', + '' => '漸', + '' => '漲', + '' => '漣', + '' => '漕', + '' => '漫', + '' => '漯', + '' => '澈', + '' => '漪', + '' => '滬', + '' => '漁', + '' => '滲', + '' => '滌', + '' => '滷', + '' => '熔', + '' => '熙', + '' => '煽', + '' => '熊', + '' => '熄', + '' => '熒', + '' => '爾', + '' => '犒', + '' => '犖', + '' => '獄', + '' => '獐', + '' => '瑤', + '' => '瑣', + '' => '瑪', + '' => '瑰', + '' => '瑭', + '' => '甄', + '' => '疑', + '' => '瘧', + '' => '瘍', + '' => '瘋', + '' => '瘉', + '' => '瘓', + '' => '盡', + '' => '監', + '' => '瞄', + '' => '睽', + '' => '睿', + '' => '睡', + '' => '磁', + '' => '碟', + '' => '碧', + '' => '碳', + '' => '碩', + '' => '碣', + '' => '禎', + '' => '福', + '' => '禍', + '' => '種', + '' => '稱', + '' => '窪', + '' => '窩', + '' => '竭', + '' => '端', + '' => '管', + '' => '箕', + '' => '箋', + '' => '筵', + '' => '算', + '' => '箝', + '' => '箔', + '' => '箏', + '' => '箸', + '' => '箇', + '' => '箄', + '' => '粹', + '' => '粽', + '' => '精', + '' => '綻', + '' => '綰', + '' => '綜', + '' => '綽', + '' => '綾', + '' => '綠', + '' => '緊', + '' => '綴', + '' => '網', + '' => '綱', + '' => '綺', + '' => '綢', + '' => '綿', + '' => '綵', + '' => '綸', + '' => '維', + '' => '緒', + '' => '緇', + '' => '綬', + '@' => '罰', + 'A' => '翠', + 'B' => '翡', + 'C' => '翟', + 'D' => '聞', + 'E' => '聚', + 'F' => '肇', + 'G' => '腐', + 'H' => '膀', + 'I' => '膏', + 'J' => '膈', + 'K' => '膊', + 'L' => '腿', + 'M' => '膂', + 'N' => '臧', + 'O' => '臺', + 'P' => '與', + 'Q' => '舔', + 'R' => '舞', + 'S' => '艋', + 'T' => '蓉', + 'U' => '蒿', + 'V' => '蓆', + 'W' => '蓄', + 'X' => '蒙', + 'Y' => '蒞', + 'Z' => '蒲', + '[' => '蒜', + '\\' => '蓋', + ']' => '蒸', + '^' => '蓀', + '_' => '蓓', + '`' => '蒐', + 'a' => '蒼', + 'b' => '蓑', + 'c' => '蓊', + 'd' => '蜿', + 'e' => '蜜', + 'f' => '蜻', + 'g' => '蜢', + 'h' => '蜥', + 'i' => '蜴', + 'j' => '蜘', + 'k' => '蝕', + 'l' => '蜷', + 'm' => '蜩', + 'n' => '裳', + 'o' => '褂', + 'p' => '裴', + 'q' => '裹', + 'r' => '裸', + 's' => '製', + 't' => '裨', + 'u' => '褚', + 'v' => '裯', + 'w' => '誦', + 'x' => '誌', + 'y' => '語', + 'z' => '誣', + '{' => '認', + '|' => '誡', + '}' => '誓', + '~' => '誤', + '' => '說', + '' => '誥', + '' => '誨', + '' => '誘', + '' => '誑', + '' => '誚', + '' => '誧', + '' => '豪', + '' => '貍', + '' => '貌', + '' => '賓', + '' => '賑', + '' => '賒', + '' => '赫', + '' => '趙', + '' => '趕', + '' => '跼', + '' => '輔', + '' => '輒', + '' => '輕', + '' => '輓', + '' => '辣', + '' => '遠', + '' => '遘', + '' => '遜', + '' => '遣', + '' => '遙', + '' => '遞', + '' => '遢', + '' => '遝', + '' => '遛', + '' => '鄙', + '' => '鄘', + '' => '鄞', + '' => '酵', + '' => '酸', + '' => '酷', + '' => '酴', + '' => '鉸', + '' => '銀', + '' => '銅', + '' => '銘', + '' => '銖', + '' => '鉻', + '' => '銓', + '' => '銜', + '' => '銨', + '' => '鉼', + '' => '銑', + '' => '閡', + '' => '閨', + '' => '閩', + '' => '閣', + '' => '閥', + '' => '閤', + '' => '隙', + '' => '障', + '' => '際', + '' => '雌', + '' => '雒', + '' => '需', + '' => '靼', + '' => '鞅', + '' => '韶', + '' => '頗', + '' => '領', + '' => '颯', + '' => '颱', + '' => '餃', + '' => '餅', + '' => '餌', + '' => '餉', + '' => '駁', + '' => '骯', + '' => '骰', + '' => '髦', + '' => '魁', + '' => '魂', + '' => '鳴', + '' => '鳶', + '' => '鳳', + '' => '麼', + '' => '鼻', + '' => '齊', + '' => '億', + '' => '儀', + '' => '僻', + '' => '僵', + '' => '價', + '' => '儂', + '' => '儈', + '' => '儉', + '' => '儅', + '' => '凜', + '@' => '劇', + 'A' => '劈', + 'B' => '劉', + 'C' => '劍', + 'D' => '劊', + 'E' => '勰', + 'F' => '厲', + 'G' => '嘮', + 'H' => '嘻', + 'I' => '嘹', + 'J' => '嘲', + 'K' => '嘿', + 'L' => '嘴', + 'M' => '嘩', + 'N' => '噓', + 'O' => '噎', + 'P' => '噗', + 'Q' => '噴', + 'R' => '嘶', + 'S' => '嘯', + 'T' => '嘰', + 'U' => '墀', + 'V' => '墟', + 'W' => '增', + 'X' => '墳', + 'Y' => '墜', + 'Z' => '墮', + '[' => '墩', + '\\' => '墦', + ']' => '奭', + '^' => '嬉', + '_' => '嫻', + '`' => '嬋', + 'a' => '嫵', + 'b' => '嬌', + 'c' => '嬈', + 'd' => '寮', + 'e' => '寬', + 'f' => '審', + 'g' => '寫', + 'h' => '層', + 'i' => '履', + 'j' => '嶝', + 'k' => '嶔', + 'l' => '幢', + 'm' => '幟', + 'n' => '幡', + 'o' => '廢', + 'p' => '廚', + 'q' => '廟', + 'r' => '廝', + 's' => '廣', + 't' => '廠', + 'u' => '彈', + 'v' => '影', + 'w' => '德', + 'x' => '徵', + 'y' => '慶', + 'z' => '慧', + '{' => '慮', + '|' => '慝', + '}' => '慕', + '~' => '憂', + '' => '慼', + '' => '慰', + '' => '慫', + '' => '慾', + '' => '憧', + '' => '憐', + '' => '憫', + '' => '憎', + '' => '憬', + '' => '憚', + '' => '憤', + '' => '憔', + '' => '憮', + '' => '戮', + '' => '摩', + '' => '摯', + '' => '摹', + '' => '撞', + '' => '撲', + '' => '撈', + '' => '撐', + '' => '撰', + '' => '撥', + '' => '撓', + '' => '撕', + '' => '撩', + '' => '撒', + '' => '撮', + '' => '播', + '' => '撫', + '' => '撚', + '' => '撬', + '' => '撙', + '' => '撢', + '' => '撳', + '' => '敵', + '' => '敷', + '' => '數', + '' => '暮', + '' => '暫', + '' => '暴', + '' => '暱', + '' => '樣', + '' => '樟', + '' => '槨', + '' => '樁', + '' => '樞', + '' => '標', + '' => '槽', + '' => '模', + '' => '樓', + '' => '樊', + '' => '槳', + '' => '樂', + '' => '樅', + '' => '槭', + '' => '樑', + '' => '歐', + '' => '歎', + '' => '殤', + '' => '毅', + '' => '毆', + '' => '漿', + '' => '潼', + '' => '澄', + '' => '潑', + '' => '潦', + '' => '潔', + '' => '澆', + '' => '潭', + '' => '潛', + '' => '潸', + '' => '潮', + '' => '澎', + '' => '潺', + '' => '潰', + '' => '潤', + '' => '澗', + '' => '潘', + '' => '滕', + '' => '潯', + '' => '潠', + '' => '潟', + '' => '熟', + '' => '熬', + '' => '熱', + '' => '熨', + '' => '牖', + '' => '犛', + '' => '獎', + '' => '獗', + '' => '瑩', + '' => '璋', + '' => '璃', + '@' => '瑾', + 'A' => '璀', + 'B' => '畿', + 'C' => '瘠', + 'D' => '瘩', + 'E' => '瘟', + 'F' => '瘤', + 'G' => '瘦', + 'H' => '瘡', + 'I' => '瘢', + 'J' => '皚', + 'K' => '皺', + 'L' => '盤', + 'M' => '瞎', + 'N' => '瞇', + 'O' => '瞌', + 'P' => '瞑', + 'Q' => '瞋', + 'R' => '磋', + 'S' => '磅', + 'T' => '確', + 'U' => '磊', + 'V' => '碾', + 'W' => '磕', + 'X' => '碼', + 'Y' => '磐', + 'Z' => '稿', + '[' => '稼', + '\\' => '穀', + ']' => '稽', + '^' => '稷', + '_' => '稻', + '`' => '窯', + 'a' => '窮', + 'b' => '箭', + 'c' => '箱', + 'd' => '範', + 'e' => '箴', + 'f' => '篆', + 'g' => '篇', + 'h' => '篁', + 'i' => '箠', + 'j' => '篌', + 'k' => '糊', + 'l' => '締', + 'm' => '練', + 'n' => '緯', + 'o' => '緻', + 'p' => '緘', + 'q' => '緬', + 'r' => '緝', + 's' => '編', + 't' => '緣', + 'u' => '線', + 'v' => '緞', + 'w' => '緩', + 'x' => '綞', + 'y' => '緙', + 'z' => '緲', + '{' => '緹', + '|' => '罵', + '}' => '罷', + '~' => '羯', + '' => '翩', + '' => '耦', + '' => '膛', + '' => '膜', + '' => '膝', + '' => '膠', + '' => '膚', + '' => '膘', + '' => '蔗', + '' => '蔽', + '' => '蔚', + '' => '蓮', + '' => '蔬', + '' => '蔭', + '' => '蔓', + '' => '蔑', + '' => '蔣', + '' => '蔡', + '' => '蔔', + '' => '蓬', + '' => '蔥', + '' => '蓿', + '' => '蔆', + '' => '螂', + '' => '蝴', + '' => '蝶', + '' => '蝠', + '' => '蝦', + '' => '蝸', + '' => '蝨', + '' => '蝙', + '' => '蝗', + '' => '蝌', + '' => '蝓', + '' => '衛', + '' => '衝', + '' => '褐', + '' => '複', + '' => '褒', + '' => '褓', + '' => '褕', + '' => '褊', + '' => '誼', + '' => '諒', + '' => '談', + '' => '諄', + '' => '誕', + '' => '請', + '' => '諸', + '' => '課', + '' => '諉', + '' => '諂', + '' => '調', + '' => '誰', + '' => '論', + '' => '諍', + '' => '誶', + '' => '誹', + '' => '諛', + '' => '豌', + '' => '豎', + '' => '豬', + '' => '賠', + '' => '賞', + '' => '賦', + '' => '賤', + '' => '賬', + '' => '賭', + '' => '賢', + '' => '賣', + '' => '賜', + '' => '質', + '' => '賡', + '' => '赭', + '' => '趟', + '' => '趣', + '' => '踫', + '' => '踐', + '' => '踝', + '' => '踢', + '' => '踏', + '' => '踩', + '' => '踟', + '' => '踡', + '' => '踞', + '' => '躺', + '' => '輝', + '' => '輛', + '' => '輟', + '' => '輩', + '' => '輦', + '' => '輪', + '' => '輜', + '' => '輞', + '@' => '輥', + 'A' => '適', + 'B' => '遮', + 'C' => '遨', + 'D' => '遭', + 'E' => '遷', + 'F' => '鄰', + 'G' => '鄭', + 'H' => '鄧', + 'I' => '鄱', + 'J' => '醇', + 'K' => '醉', + 'L' => '醋', + 'M' => '醃', + 'N' => '鋅', + 'O' => '銻', + 'P' => '銷', + 'Q' => '鋪', + 'R' => '銬', + 'S' => '鋤', + 'T' => '鋁', + 'U' => '銳', + 'V' => '銼', + 'W' => '鋒', + 'X' => '鋇', + 'Y' => '鋰', + 'Z' => '銲', + '[' => '閭', + '\\' => '閱', + ']' => '霄', + '^' => '霆', + '_' => '震', + '`' => '霉', + 'a' => '靠', + 'b' => '鞍', + 'c' => '鞋', + 'd' => '鞏', + 'e' => '頡', + 'f' => '頫', + 'g' => '頜', + 'h' => '颳', + 'i' => '養', + 'j' => '餓', + 'k' => '餒', + 'l' => '餘', + 'm' => '駝', + 'n' => '駐', + 'o' => '駟', + 'p' => '駛', + 'q' => '駑', + 'r' => '駕', + 's' => '駒', + 't' => '駙', + 'u' => '骷', + 'v' => '髮', + 'w' => '髯', + 'x' => '鬧', + 'y' => '魅', + 'z' => '魄', + '{' => '魷', + '|' => '魯', + '}' => '鴆', + '~' => '鴉', + '' => '鴃', + '' => '麩', + '' => '麾', + '' => '黎', + '' => '墨', + '' => '齒', + '' => '儒', + '' => '儘', + '' => '儔', + '' => '儐', + '' => '儕', + '' => '冀', + '' => '冪', + '' => '凝', + '' => '劑', + '' => '劓', + '' => '勳', + '' => '噙', + '' => '噫', + '' => '噹', + '' => '噩', + '' => '噤', + '' => '噸', + '' => '噪', + '' => '器', + '' => '噥', + '' => '噱', + '' => '噯', + '' => '噬', + '' => '噢', + '' => '噶', + '' => '壁', + '' => '墾', + '' => '壇', + '' => '壅', + '' => '奮', + '' => '嬝', + '' => '嬴', + '' => '學', + '' => '寰', + '' => '導', + '' => '彊', + '' => '憲', + '' => '憑', + '' => '憩', + '' => '憊', + '' => '懍', + '' => '憶', + '' => '憾', + '' => '懊', + '' => '懈', + '' => '戰', + '' => '擅', + '' => '擁', + '' => '擋', + '' => '撻', + '' => '撼', + '' => '據', + '' => '擄', + '' => '擇', + '' => '擂', + '' => '操', + '' => '撿', + '' => '擒', + '' => '擔', + '' => '撾', + '' => '整', + '' => '曆', + '' => '曉', + '' => '暹', + '' => '曄', + '' => '曇', + '' => '暸', + '' => '樽', + '' => '樸', + '' => '樺', + '' => '橙', + '' => '橫', + '' => '橘', + '' => '樹', + '' => '橄', + '' => '橢', + '' => '橡', + '' => '橋', + '' => '橇', + '' => '樵', + '' => '機', + '' => '橈', + '' => '歙', + '' => '歷', + '' => '氅', + '' => '濂', + '' => '澱', + '' => '澡', + '@' => '濃', + 'A' => '澤', + 'B' => '濁', + 'C' => '澧', + 'D' => '澳', + 'E' => '激', + 'F' => '澹', + 'G' => '澶', + 'H' => '澦', + 'I' => '澠', + 'J' => '澴', + 'K' => '熾', + 'L' => '燉', + 'M' => '燐', + 'N' => '燒', + 'O' => '燈', + 'P' => '燕', + 'Q' => '熹', + 'R' => '燎', + 'S' => '燙', + 'T' => '燜', + 'U' => '燃', + 'V' => '燄', + 'W' => '獨', + 'X' => '璜', + 'Y' => '璣', + 'Z' => '璘', + '[' => '璟', + '\\' => '璞', + ']' => '瓢', + '^' => '甌', + '_' => '甍', + '`' => '瘴', + 'a' => '瘸', + 'b' => '瘺', + 'c' => '盧', + 'd' => '盥', + 'e' => '瞠', + 'f' => '瞞', + 'g' => '瞟', + 'h' => '瞥', + 'i' => '磨', + 'j' => '磚', + 'k' => '磬', + 'l' => '磧', + 'm' => '禦', + 'n' => '積', + 'o' => '穎', + 'p' => '穆', + 'q' => '穌', + 'r' => '穋', + 's' => '窺', + 't' => '篙', + 'u' => '簑', + 'v' => '築', + 'w' => '篤', + 'x' => '篛', + 'y' => '篡', + 'z' => '篩', + '{' => '篦', + '|' => '糕', + '}' => '糖', + '~' => '縊', + '' => '縑', + '' => '縈', + '' => '縛', + '' => '縣', + '' => '縞', + '' => '縝', + '' => '縉', + '' => '縐', + '' => '罹', + '' => '羲', + '' => '翰', + '' => '翱', + '' => '翮', + '' => '耨', + '' => '膳', + '' => '膩', + '' => '膨', + '' => '臻', + '' => '興', + '' => '艘', + '' => '艙', + '' => '蕊', + '' => '蕙', + '' => '蕈', + '' => '蕨', + '' => '蕩', + '' => '蕃', + '' => '蕉', + '' => '蕭', + '' => '蕪', + '' => '蕞', + '' => '螃', + '' => '螟', + '' => '螞', + '' => '螢', + '' => '融', + '' => '衡', + '' => '褪', + '' => '褲', + '' => '褥', + '' => '褫', + '' => '褡', + '' => '親', + '' => '覦', + '' => '諦', + '' => '諺', + '' => '諫', + '' => '諱', + '' => '謀', + '' => '諜', + '' => '諧', + '' => '諮', + '' => '諾', + '' => '謁', + '' => '謂', + '' => '諷', + '' => '諭', + '' => '諳', + '' => '諶', + '' => '諼', + '' => '豫', + '' => '豭', + '' => '貓', + '' => '賴', + '' => '蹄', + '' => '踱', + '' => '踴', + '' => '蹂', + '' => '踹', + '' => '踵', + '' => '輻', + '' => '輯', + '' => '輸', + '' => '輳', + '' => '辨', + '' => '辦', + '' => '遵', + '' => '遴', + '' => '選', + '' => '遲', + '' => '遼', + '' => '遺', + '' => '鄴', + '' => '醒', + '' => '錠', + '' => '錶', + '' => '鋸', + '' => '錳', + '' => '錯', + '' => '錢', + '' => '鋼', + '' => '錫', + '' => '錄', + '' => '錚', + '@' => '錐', + 'A' => '錦', + 'B' => '錡', + 'C' => '錕', + 'D' => '錮', + 'E' => '錙', + 'F' => '閻', + 'G' => '隧', + 'H' => '隨', + 'I' => '險', + 'J' => '雕', + 'K' => '霎', + 'L' => '霑', + 'M' => '霖', + 'N' => '霍', + 'O' => '霓', + 'P' => '霏', + 'Q' => '靛', + 'R' => '靜', + 'S' => '靦', + 'T' => '鞘', + 'U' => '頰', + 'V' => '頸', + 'W' => '頻', + 'X' => '頷', + 'Y' => '頭', + 'Z' => '頹', + '[' => '頤', + '\\' => '餐', + ']' => '館', + '^' => '餞', + '_' => '餛', + '`' => '餡', + 'a' => '餚', + 'b' => '駭', + 'c' => '駢', + 'd' => '駱', + 'e' => '骸', + 'f' => '骼', + 'g' => '髻', + 'h' => '髭', + 'i' => '鬨', + 'j' => '鮑', + 'k' => '鴕', + 'l' => '鴣', + 'm' => '鴦', + 'n' => '鴨', + 'o' => '鴒', + 'p' => '鴛', + 'q' => '默', + 'r' => '黔', + 's' => '龍', + 't' => '龜', + 'u' => '優', + 'v' => '償', + 'w' => '儡', + 'x' => '儲', + 'y' => '勵', + 'z' => '嚎', + '{' => '嚀', + '|' => '嚐', + '}' => '嚅', + '~' => '嚇', + '' => '嚏', + '' => '壕', + '' => '壓', + '' => '壑', + '' => '壎', + '' => '嬰', + '' => '嬪', + '' => '嬤', + '' => '孺', + '' => '尷', + '' => '屨', + '' => '嶼', + '' => '嶺', + '' => '嶽', + '' => '嶸', + '' => '幫', + '' => '彌', + '' => '徽', + '' => '應', + '' => '懂', + '' => '懇', + '' => '懦', + '' => '懋', + '' => '戲', + '' => '戴', + '' => '擎', + '' => '擊', + '' => '擘', + '' => '擠', + '' => '擰', + '' => '擦', + '' => '擬', + '' => '擱', + '' => '擢', + '' => '擭', + '' => '斂', + '' => '斃', + '' => '曙', + '' => '曖', + '' => '檀', + '' => '檔', + '' => '檄', + '' => '檢', + '' => '檜', + '' => '櫛', + '' => '檣', + '' => '橾', + '' => '檗', + '' => '檐', + '' => '檠', + '' => '歜', + '' => '殮', + '' => '毚', + '' => '氈', + '' => '濘', + '' => '濱', + '' => '濟', + '' => '濠', + '' => '濛', + '' => '濤', + '' => '濫', + '' => '濯', + '' => '澀', + '' => '濬', + '' => '濡', + '' => '濩', + '' => '濕', + '' => '濮', + '' => '濰', + '' => '燧', + '' => '營', + '' => '燮', + '' => '燦', + '' => '燥', + '' => '燭', + '' => '燬', + '' => '燴', + '' => '燠', + '' => '爵', + '' => '牆', + '' => '獰', + '' => '獲', + '' => '璩', + '' => '環', + '' => '璦', + '' => '璨', + '' => '癆', + '' => '療', + '' => '癌', + '' => '盪', + '' => '瞳', + '' => '瞪', + '' => '瞰', + '' => '瞬', + '@' => '瞧', + 'A' => '瞭', + 'B' => '矯', + 'C' => '磷', + 'D' => '磺', + 'E' => '磴', + 'F' => '磯', + 'G' => '礁', + 'H' => '禧', + 'I' => '禪', + 'J' => '穗', + 'K' => '窿', + 'L' => '簇', + 'M' => '簍', + 'N' => '篾', + 'O' => '篷', + 'P' => '簌', + 'Q' => '篠', + 'R' => '糠', + 'S' => '糜', + 'T' => '糞', + 'U' => '糢', + 'V' => '糟', + 'W' => '糙', + 'X' => '糝', + 'Y' => '縮', + 'Z' => '績', + '[' => '繆', + '\\' => '縷', + ']' => '縲', + '^' => '繃', + '_' => '縫', + '`' => '總', + 'a' => '縱', + 'b' => '繅', + 'c' => '繁', + 'd' => '縴', + 'e' => '縹', + 'f' => '繈', + 'g' => '縵', + 'h' => '縿', + 'i' => '縯', + 'j' => '罄', + 'k' => '翳', + 'l' => '翼', + 'm' => '聱', + 'n' => '聲', + 'o' => '聰', + 'p' => '聯', + 'q' => '聳', + 'r' => '臆', + 's' => '臃', + 't' => '膺', + 'u' => '臂', + 'v' => '臀', + 'w' => '膿', + 'x' => '膽', + 'y' => '臉', + 'z' => '膾', + '{' => '臨', + '|' => '舉', + '}' => '艱', + '~' => '薪', + '' => '薄', + '' => '蕾', + '' => '薜', + '' => '薑', + '' => '薔', + '' => '薯', + '' => '薛', + '' => '薇', + '' => '薨', + '' => '薊', + '' => '虧', + '' => '蟀', + '' => '蟑', + '' => '螳', + '' => '蟒', + '' => '蟆', + '' => '螫', + '' => '螻', + '' => '螺', + '' => '蟈', + '' => '蟋', + '' => '褻', + '' => '褶', + '' => '襄', + '' => '褸', + '' => '褽', + '' => '覬', + '' => '謎', + '' => '謗', + '' => '謙', + '' => '講', + '' => '謊', + '' => '謠', + '' => '謝', + '' => '謄', + '' => '謐', + '' => '豁', + '' => '谿', + '' => '豳', + '' => '賺', + '' => '賽', + '' => '購', + '' => '賸', + '' => '賻', + '' => '趨', + '' => '蹉', + '' => '蹋', + '' => '蹈', + '' => '蹊', + '' => '轄', + '' => '輾', + '' => '轂', + '' => '轅', + '' => '輿', + '' => '避', + '' => '遽', + '' => '還', + '' => '邁', + '' => '邂', + '' => '邀', + '' => '鄹', + '' => '醣', + '' => '醞', + '' => '醜', + '' => '鍍', + '' => '鎂', + '' => '錨', + '' => '鍵', + '' => '鍊', + '' => '鍥', + '' => '鍋', + '' => '錘', + '' => '鍾', + '' => '鍬', + '' => '鍛', + '' => '鍰', + '' => '鍚', + '' => '鍔', + '' => '闊', + '' => '闋', + '' => '闌', + '' => '闈', + '' => '闆', + '' => '隱', + '' => '隸', + '' => '雖', + '' => '霜', + '' => '霞', + '' => '鞠', + '' => '韓', + '' => '顆', + '' => '颶', + '' => '餵', + '' => '騁', + '@' => '駿', + 'A' => '鮮', + 'B' => '鮫', + 'C' => '鮪', + 'D' => '鮭', + 'E' => '鴻', + 'F' => '鴿', + 'G' => '麋', + 'H' => '黏', + 'I' => '點', + 'J' => '黜', + 'K' => '黝', + 'L' => '黛', + 'M' => '鼾', + 'N' => '齋', + 'O' => '叢', + 'P' => '嚕', + 'Q' => '嚮', + 'R' => '壙', + 'S' => '壘', + 'T' => '嬸', + 'U' => '彝', + 'V' => '懣', + 'W' => '戳', + 'X' => '擴', + 'Y' => '擲', + 'Z' => '擾', + '[' => '攆', + '\\' => '擺', + ']' => '擻', + '^' => '擷', + '_' => '斷', + '`' => '曜', + 'a' => '朦', + 'b' => '檳', + 'c' => '檬', + 'd' => '櫃', + 'e' => '檻', + 'f' => '檸', + 'g' => '櫂', + 'h' => '檮', + 'i' => '檯', + 'j' => '歟', + 'k' => '歸', + 'l' => '殯', + 'm' => '瀉', + 'n' => '瀋', + 'o' => '濾', + 'p' => '瀆', + 'q' => '濺', + 'r' => '瀑', + 's' => '瀏', + 't' => '燻', + 'u' => '燼', + 'v' => '燾', + 'w' => '燸', + 'x' => '獷', + 'y' => '獵', + 'z' => '璧', + '{' => '璿', + '|' => '甕', + '}' => '癖', + '~' => '癘', + '¡' => '癒', + '¢' => '瞽', + '£' => '瞿', + '¤' => '瞻', + '¥' => '瞼', + '¦' => '礎', + '§' => '禮', + '¨' => '穡', + '©' => '穢', + 'ª' => '穠', + '«' => '竄', + '¬' => '竅', + '­' => '簫', + '®' => '簧', + '¯' => '簪', + '°' => '簞', + '±' => '簣', + '²' => '簡', + '³' => '糧', + '´' => '織', + 'µ' => '繕', + '¶' => '繞', + '·' => '繚', + '¸' => '繡', + '¹' => '繒', + 'º' => '繙', + '»' => '罈', + '¼' => '翹', + '½' => '翻', + '¾' => '職', + '¿' => '聶', + '' => '臍', + '' => '臏', + '' => '舊', + '' => '藏', + '' => '薩', + '' => '藍', + '' => '藐', + '' => '藉', + '' => '薰', + '' => '薺', + '' => '薹', + '' => '薦', + '' => '蟯', + '' => '蟬', + '' => '蟲', + '' => '蟠', + '' => '覆', + '' => '覲', + '' => '觴', + '' => '謨', + '' => '謹', + '' => '謬', + '' => '謫', + '' => '豐', + '' => '贅', + '' => '蹙', + '' => '蹣', + '' => '蹦', + '' => '蹤', + '' => '蹟', + '' => '蹕', + '' => '軀', + '' => '轉', + '' => '轍', + '' => '邇', + '' => '邃', + '' => '邈', + '' => '醫', + '' => '醬', + '' => '釐', + '' => '鎔', + '' => '鎊', + '' => '鎖', + '' => '鎢', + '' => '鎳', + '' => '鎮', + '' => '鎬', + '' => '鎰', + '' => '鎘', + '' => '鎚', + '' => '鎗', + '' => '闔', + '' => '闖', + '' => '闐', + '' => '闕', + '' => '離', + '' => '雜', + '' => '雙', + '' => '雛', + '' => '雞', + '' => '霤', + '' => '鞣', + '' => '鞦', + '@' => '鞭', + 'A' => '韹', + 'B' => '額', + 'C' => '顏', + 'D' => '題', + 'E' => '顎', + 'F' => '顓', + 'G' => '颺', + 'H' => '餾', + 'I' => '餿', + 'J' => '餽', + 'K' => '餮', + 'L' => '馥', + 'M' => '騎', + 'N' => '髁', + 'O' => '鬃', + 'P' => '鬆', + 'Q' => '魏', + 'R' => '魎', + 'S' => '魍', + 'T' => '鯊', + 'U' => '鯉', + 'V' => '鯽', + 'W' => '鯈', + 'X' => '鯀', + 'Y' => '鵑', + 'Z' => '鵝', + '[' => '鵠', + '\\' => '黠', + ']' => '鼕', + '^' => '鼬', + '_' => '儳', + '`' => '嚥', + 'a' => '壞', + 'b' => '壟', + 'c' => '壢', + 'd' => '寵', + 'e' => '龐', + 'f' => '廬', + 'g' => '懲', + 'h' => '懷', + 'i' => '懶', + 'j' => '懵', + 'k' => '攀', + 'l' => '攏', + 'm' => '曠', + 'n' => '曝', + 'o' => '櫥', + 'p' => '櫝', + 'q' => '櫚', + 'r' => '櫓', + 's' => '瀛', + 't' => '瀟', + 'u' => '瀨', + 'v' => '瀚', + 'w' => '瀝', + 'x' => '瀕', + 'y' => '瀘', + 'z' => '爆', + '{' => '爍', + '|' => '牘', + '}' => '犢', + '~' => '獸', + 'á' => '獺', + 'â' => '璽', + 'ã' => '瓊', + 'ä' => '瓣', + 'å' => '疇', + 'æ' => '疆', + 'ç' => '癟', + 'è' => '癡', + 'é' => '矇', + 'ê' => '礙', + 'ë' => '禱', + 'ì' => '穫', + 'í' => '穩', + 'î' => '簾', + 'ï' => '簿', + 'ð' => '簸', + 'ñ' => '簽', + 'ò' => '簷', + 'ó' => '籀', + 'ô' => '繫', + 'õ' => '繭', + 'ö' => '繹', + '÷' => '繩', + 'ø' => '繪', + 'ù' => '羅', + 'ú' => '繳', + 'û' => '羶', + 'ü' => '羹', + 'ý' => '羸', + 'þ' => '臘', + 'ÿ' => '藩', + '' => '藝', + '' => '藪', + '' => '藕', + '' => '藤', + '' => '藥', + '' => '藷', + '' => '蟻', + '' => '蠅', + '' => '蠍', + '' => '蟹', + '' => '蟾', + '' => '襠', + '' => '襟', + '' => '襖', + '' => '襞', + '' => '譁', + '' => '譜', + '' => '識', + '' => '證', + '' => '譚', + '' => '譎', + '' => '譏', + '' => '譆', + '' => '譙', + '' => '贈', + '' => '贊', + '' => '蹼', + '' => '蹲', + '' => '躇', + '' => '蹶', + '' => '蹬', + '' => '蹺', + '' => '蹴', + '' => '轔', + '' => '轎', + '' => '辭', + '' => '邊', + '' => '邋', + '' => '醱', + '' => '醮', + '' => '鏡', + '' => '鏑', + '' => '鏟', + '' => '鏃', + '' => '鏈', + '' => '鏜', + '' => '鏝', + '' => '鏖', + '' => '鏢', + '' => '鏍', + '' => '鏘', + '' => '鏤', + '' => '鏗', + '' => '鏨', + '' => '關', + '' => '隴', + '' => '難', + '' => '霪', + '' => '霧', + '' => '靡', + '' => '韜', + '' => '韻', + '' => '類', + '@' => '願', + 'A' => '顛', + 'B' => '颼', + 'C' => '饅', + 'D' => '饉', + 'E' => '騖', + 'F' => '騙', + 'G' => '鬍', + 'H' => '鯨', + 'I' => '鯧', + 'J' => '鯖', + 'K' => '鯛', + 'L' => '鶉', + 'M' => '鵡', + 'N' => '鵲', + 'O' => '鵪', + 'P' => '鵬', + 'Q' => '麒', + 'R' => '麗', + 'S' => '麓', + 'T' => '麴', + 'U' => '勸', + 'V' => '嚨', + 'W' => '嚷', + 'X' => '嚶', + 'Y' => '嚴', + 'Z' => '嚼', + '[' => '壤', + '\\' => '孀', + ']' => '孃', + '^' => '孽', + '_' => '寶', + '`' => '巉', + 'a' => '懸', + 'b' => '懺', + 'c' => '攘', + 'd' => '攔', + 'e' => '攙', + 'f' => '曦', + 'g' => '朧', + 'h' => '櫬', + 'i' => '瀾', + 'j' => '瀰', + 'k' => '瀲', + 'l' => '爐', + 'm' => '獻', + 'n' => '瓏', + 'o' => '癢', + 'p' => '癥', + 'q' => '礦', + 'r' => '礪', + 's' => '礬', + 't' => '礫', + 'u' => '竇', + 'v' => '競', + 'w' => '籌', + 'x' => '籃', + 'y' => '籍', + 'z' => '糯', + '{' => '糰', + '|' => '辮', + '}' => '繽', + '~' => '繼', + 'ġ' => '纂', + 'Ģ' => '罌', + 'ģ' => '耀', + 'Ĥ' => '臚', + 'ĥ' => '艦', + 'Ħ' => '藻', + 'ħ' => '藹', + 'Ĩ' => '蘑', + 'ĩ' => '藺', + 'Ī' => '蘆', + 'ī' => '蘋', + 'Ĭ' => '蘇', + 'ĭ' => '蘊', + 'Į' => '蠔', + 'į' => '蠕', + 'İ' => '襤', + 'ı' => '覺', + 'IJ' => '觸', + 'ij' => '議', + 'Ĵ' => '譬', + 'ĵ' => '警', + 'Ķ' => '譯', + 'ķ' => '譟', + 'ĸ' => '譫', + 'Ĺ' => '贏', + 'ĺ' => '贍', + 'Ļ' => '躉', + 'ļ' => '躁', + 'Ľ' => '躅', + 'ľ' => '躂', + 'Ŀ' => '醴', + '' => '釋', + '' => '鐘', + '' => '鐃', + '' => '鏽', + '' => '闡', + '' => '霰', + '' => '飄', + '' => '饒', + '' => '饑', + '' => '馨', + '' => '騫', + '' => '騰', + '' => '騷', + '' => '騵', + '' => '鰓', + '' => '鰍', + '' => '鹹', + '' => '麵', + '' => '黨', + '' => '鼯', + '' => '齟', + '' => '齣', + '' => '齡', + '' => '儷', + '' => '儸', + '' => '囁', + '' => '囀', + '' => '囂', + '' => '夔', + '' => '屬', + '' => '巍', + '' => '懼', + '' => '懾', + '' => '攝', + '' => '攜', + '' => '斕', + '' => '曩', + '' => '櫻', + '' => '欄', + '' => '櫺', + '' => '殲', + '' => '灌', + '' => '爛', + '' => '犧', + '' => '瓖', + '' => '瓔', + '' => '癩', + '' => '矓', + '' => '籐', + '' => '纏', + '' => '續', + '' => '羼', + '' => '蘗', + '' => '蘭', + '' => '蘚', + '' => '蠣', + '' => '蠢', + '' => '蠡', + '' => '蠟', + '' => '襪', + '' => '襬', + '' => '覽', + '' => '譴', + '@' => '護', + 'A' => '譽', + 'B' => '贓', + 'C' => '躊', + 'D' => '躍', + 'E' => '躋', + 'F' => '轟', + 'G' => '辯', + 'H' => '醺', + 'I' => '鐮', + 'J' => '鐳', + 'K' => '鐵', + 'L' => '鐺', + 'M' => '鐸', + 'N' => '鐲', + 'O' => '鐫', + 'P' => '闢', + 'Q' => '霸', + 'R' => '霹', + 'S' => '露', + 'T' => '響', + 'U' => '顧', + 'V' => '顥', + 'W' => '饗', + 'X' => '驅', + 'Y' => '驃', + 'Z' => '驀', + '[' => '騾', + '\\' => '髏', + ']' => '魔', + '^' => '魑', + '_' => '鰭', + '`' => '鰥', + 'a' => '鶯', + 'b' => '鶴', + 'c' => '鷂', + 'd' => '鶸', + 'e' => '麝', + 'f' => '黯', + 'g' => '鼙', + 'h' => '齜', + 'i' => '齦', + 'j' => '齧', + 'k' => '儼', + 'l' => '儻', + 'm' => '囈', + 'n' => '囊', + 'o' => '囉', + 'p' => '孿', + 'q' => '巔', + 'r' => '巒', + 's' => '彎', + 't' => '懿', + 'u' => '攤', + 'v' => '權', + 'w' => '歡', + 'x' => '灑', + 'y' => '灘', + 'z' => '玀', + '{' => '瓤', + '|' => '疊', + '}' => '癮', + '~' => '癬', + 'š' => '禳', + 'Ţ' => '籠', + 'ţ' => '籟', + 'Ť' => '聾', + 'ť' => '聽', + 'Ŧ' => '臟', + 'ŧ' => '襲', + 'Ũ' => '襯', + 'ũ' => '觼', + 'Ū' => '讀', + 'ū' => '贖', + 'Ŭ' => '贗', + 'ŭ' => '躑', + 'Ů' => '躓', + 'ů' => '轡', + 'Ű' => '酈', + 'ű' => '鑄', + 'Ų' => '鑑', + 'ų' => '鑒', + 'Ŵ' => '霽', + 'ŵ' => '霾', + 'Ŷ' => '韃', + 'ŷ' => '韁', + 'Ÿ' => '顫', + 'Ź' => '饕', + 'ź' => '驕', + 'Ż' => '驍', + 'ż' => '髒', + 'Ž' => '鬚', + 'ž' => '鱉', + 'ſ' => '鰱', + '' => '鰾', + '' => '鰻', + '' => '鷓', + '' => '鷗', + '' => '鼴', + '' => '齬', + '' => '齪', + '' => '龔', + '' => '囌', + '' => '巖', + '' => '戀', + '' => '攣', + '' => '攫', + '' => '攪', + '' => '曬', + '' => '欐', + '' => '瓚', + '' => '竊', + '' => '籤', + '' => '籣', + '' => '籥', + '' => '纓', + '' => '纖', + '' => '纔', + '' => '臢', + '' => '蘸', + '' => '蘿', + '' => '蠱', + '' => '變', + '' => '邐', + '' => '邏', + '' => '鑣', + '' => '鑠', + '' => '鑤', + '' => '靨', + '' => '顯', + '' => '饜', + '' => '驚', + '' => '驛', + '' => '驗', + '' => '髓', + '' => '體', + '' => '髑', + '' => '鱔', + '' => '鱗', + '' => '鱖', + '' => '鷥', + '' => '麟', + '' => '黴', + '' => '囑', + '' => '壩', + '' => '攬', + '' => '灞', + '' => '癱', + '' => '癲', + '' => '矗', + '' => '罐', + '' => '羈', + '' => '蠶', + '' => '蠹', + '' => '衢', + '' => '讓', + '' => '讒', + '@' => '讖', + 'A' => '艷', + 'B' => '贛', + 'C' => '釀', + 'D' => '鑪', + 'E' => '靂', + 'F' => '靈', + 'G' => '靄', + 'H' => '韆', + 'I' => '顰', + 'J' => '驟', + 'K' => '鬢', + 'L' => '魘', + 'M' => '鱟', + 'N' => '鷹', + 'O' => '鷺', + 'P' => '鹼', + 'Q' => '鹽', + 'R' => '鼇', + 'S' => '齷', + 'T' => '齲', + 'U' => '廳', + 'V' => '欖', + 'W' => '灣', + 'X' => '籬', + 'Y' => '籮', + 'Z' => '蠻', + '[' => '觀', + '\\' => '躡', + ']' => '釁', + '^' => '鑲', + '_' => '鑰', + '`' => '顱', + 'a' => '饞', + 'b' => '髖', + 'c' => '鬣', + 'd' => '黌', + 'e' => '灤', + 'f' => '矚', + 'g' => '讚', + 'h' => '鑷', + 'i' => '韉', + 'j' => '驢', + 'k' => '驥', + 'l' => '纜', + 'm' => '讜', + 'n' => '躪', + 'o' => '釅', + 'p' => '鑽', + 'q' => '鑾', + 'r' => '鑼', + 's' => '鱷', + 't' => '鱸', + 'u' => '黷', + 'v' => '豔', + 'w' => '鑿', + 'x' => '鸚', + 'y' => '爨', + 'z' => '驪', + '{' => '鬱', + '|' => '鸛', + '}' => '鸞', + '~' => '籲', + 'ơ' => 'ヾ', + 'Ƣ' => 'ゝ', + 'ƣ' => 'ゞ', + 'Ƥ' => '々', + 'ƥ' => 'ぁ', + 'Ʀ' => 'あ', + 'Ƨ' => 'ぃ', + 'ƨ' => 'い', + 'Ʃ' => 'ぅ', + 'ƪ' => 'う', + 'ƫ' => 'ぇ', + 'Ƭ' => 'え', + 'ƭ' => 'ぉ', + 'Ʈ' => 'お', + 'Ư' => 'か', + 'ư' => 'が', + 'Ʊ' => 'き', + 'Ʋ' => 'ぎ', + 'Ƴ' => 'く', + 'ƴ' => 'ぐ', + 'Ƶ' => 'け', + 'ƶ' => 'げ', + 'Ʒ' => 'こ', + 'Ƹ' => 'ご', + 'ƹ' => 'さ', + 'ƺ' => 'ざ', + 'ƻ' => 'し', + 'Ƽ' => 'じ', + 'ƽ' => 'す', + 'ƾ' => 'ず', + 'ƿ' => 'せ', + '' => 'ぜ', + '' => 'そ', + '' => 'ぞ', + '' => 'た', + '' => 'だ', + '' => 'ち', + '' => 'ぢ', + '' => 'っ', + '' => 'つ', + '' => 'づ', + '' => 'て', + '' => 'で', + '' => 'と', + '' => 'ど', + '' => 'な', + '' => 'に', + '' => 'ぬ', + '' => 'ね', + '' => 'の', + '' => 'は', + '' => 'ば', + '' => 'ぱ', + '' => 'ひ', + '' => 'び', + '' => 'ぴ', + '' => 'ふ', + '' => 'ぶ', + '' => 'ぷ', + '' => 'へ', + '' => 'べ', + '' => 'ぺ', + '' => 'ほ', + '' => 'ぼ', + '' => 'ぽ', + '' => 'ま', + '' => 'み', + '' => 'む', + '' => 'め', + '' => 'も', + '' => 'ゃ', + '' => 'や', + '' => 'ゅ', + '' => 'ゆ', + '' => 'ょ', + '' => 'よ', + '' => 'ら', + '' => 'り', + '' => 'る', + '' => 'れ', + '' => 'ろ', + '' => 'ゎ', + '' => 'わ', + '' => 'ゐ', + '' => 'ゑ', + '' => 'を', + '' => 'ん', + '' => 'ァ', + '' => 'ア', + '' => 'ィ', + '' => 'イ', + '' => 'ゥ', + '' => 'ウ', + '' => 'ェ', + '@' => 'エ', + 'A' => 'ォ', + 'B' => 'オ', + 'C' => 'カ', + 'D' => 'ガ', + 'E' => 'キ', + 'F' => 'ギ', + 'G' => 'ク', + 'H' => 'グ', + 'I' => 'ケ', + 'J' => 'ゲ', + 'K' => 'コ', + 'L' => 'ゴ', + 'M' => 'サ', + 'N' => 'ザ', + 'O' => 'シ', + 'P' => 'ジ', + 'Q' => 'ス', + 'R' => 'ズ', + 'S' => 'セ', + 'T' => 'ゼ', + 'U' => 'ソ', + 'V' => 'ゾ', + 'W' => 'タ', + 'X' => 'ダ', + 'Y' => 'チ', + 'Z' => 'ヂ', + '[' => 'ッ', + '\\' => 'ツ', + ']' => 'ヅ', + '^' => 'テ', + '_' => 'デ', + '`' => 'ト', + 'a' => 'ド', + 'b' => 'ナ', + 'c' => 'ニ', + 'd' => 'ヌ', + 'e' => 'ネ', + 'f' => 'ノ', + 'g' => 'ハ', + 'h' => 'バ', + 'i' => 'パ', + 'j' => 'ヒ', + 'k' => 'ビ', + 'l' => 'ピ', + 'm' => 'フ', + 'n' => 'ブ', + 'o' => 'プ', + 'p' => 'ヘ', + 'q' => 'ベ', + 'r' => 'ペ', + 's' => 'ホ', + 't' => 'ボ', + 'u' => 'ポ', + 'v' => 'マ', + 'w' => 'ミ', + 'x' => 'ム', + 'y' => 'メ', + 'z' => 'モ', + '{' => 'ャ', + '|' => 'ヤ', + '}' => 'ュ', + '~' => 'ユ', + 'ǡ' => 'ョ', + 'Ǣ' => 'ヨ', + 'ǣ' => 'ラ', + 'Ǥ' => 'リ', + 'ǥ' => 'ル', + 'Ǧ' => 'レ', + 'ǧ' => 'ロ', + 'Ǩ' => 'ヮ', + 'ǩ' => 'ワ', + 'Ǫ' => 'ヰ', + 'ǫ' => 'ヱ', + 'Ǭ' => 'ヲ', + 'ǭ' => 'ン', + 'Ǯ' => 'ヴ', + 'ǯ' => 'ヵ', + 'ǰ' => 'ヶ', + 'DZ' => 'Д', + 'Dz' => 'Е', + 'dz' => 'Ё', + 'Ǵ' => 'Ж', + 'ǵ' => 'З', + 'Ƕ' => 'И', + 'Ƿ' => 'Й', + 'Ǹ' => 'К', + 'ǹ' => 'Л', + 'Ǻ' => 'М', + 'ǻ' => 'У', + 'Ǽ' => 'Ф', + 'ǽ' => 'Х', + 'Ǿ' => 'Ц', + 'ǿ' => 'Ч', + '' => 'Ш', + '' => 'Щ', + '' => 'Ъ', + '' => 'Ы', + '' => 'Ь', + '' => 'Э', + '' => 'Ю', + '' => 'Я', + '' => 'а', + '' => 'б', + '' => 'в', + '' => 'г', + '' => 'д', + '' => 'е', + '' => 'ё', + '' => 'ж', + '' => 'з', + '' => 'и', + '' => 'й', + '' => 'к', + '' => 'л', + '' => 'м', + '' => 'н', + '' => 'о', + '' => 'п', + '' => 'р', + '' => 'с', + '' => 'т', + '' => 'у', + '' => 'ф', + '' => 'х', + '' => 'ц', + '' => 'ч', + '' => 'ш', + '' => 'щ', + '' => 'ъ', + '' => 'ы', + '' => 'ь', + '' => 'э', + '' => 'ю', + '' => 'я', + '' => '①', + '' => '②', + '' => '③', + '' => '④', + '' => '⑤', + '' => '⑥', + '' => '⑦', + '' => '⑧', + '' => '⑨', + '' => '⑩', + '' => '⑴', + '' => '⑵', + '' => '⑶', + '' => '⑷', + '' => '⑸', + '' => '⑹', + '' => '⑺', + '' => '⑻', + '' => '⑼', + '' => '⑽', + '@' => '乂', + 'A' => '乜', + 'B' => '凵', + 'C' => '匚', + 'D' => '厂', + 'E' => '万', + 'F' => '丌', + 'G' => '乇', + 'H' => '亍', + 'I' => '囗', + 'J' => '兀', + 'K' => '屮', + 'L' => '彳', + 'M' => '丏', + 'N' => '冇', + 'O' => '与', + 'P' => '丮', + 'Q' => '亓', + 'R' => '仂', + 'S' => '仉', + 'T' => '仈', + 'U' => '冘', + 'V' => '勼', + 'W' => '卬', + 'X' => '厹', + 'Y' => '圠', + 'Z' => '夃', + '[' => '夬', + '\\' => '尐', + ']' => '巿', + '^' => '旡', + '_' => '殳', + '`' => '毌', + 'a' => '气', + 'b' => '爿', + 'c' => '丱', + 'd' => '丼', + 'e' => '仨', + 'f' => '仜', + 'g' => '仩', + 'h' => '仡', + 'i' => '仝', + 'j' => '仚', + 'k' => '刌', + 'l' => '匜', + 'm' => '卌', + 'n' => '圢', + 'o' => '圣', + 'p' => '夗', + 'q' => '夯', + 'r' => '宁', + 's' => '宄', + 't' => '尒', + 'u' => '尻', + 'v' => '屴', + 'w' => '屳', + 'x' => '帄', + 'y' => '庀', + 'z' => '庂', + '{' => '忉', + '|' => '戉', + '}' => '扐', + '~' => '氕', + 'ɡ' => '氶', + 'ɢ' => '汃', + 'ɣ' => '氿', + 'ɤ' => '氻', + 'ɥ' => '犮', + 'ɦ' => '犰', + 'ɧ' => '玊', + 'ɨ' => '禸', + 'ɩ' => '肊', + 'ɪ' => '阞', + 'ɫ' => '伎', + 'ɬ' => '优', + 'ɭ' => '伬', + 'ɮ' => '仵', + 'ɯ' => '伔', + 'ɰ' => '仱', + 'ɱ' => '伀', + 'ɲ' => '价', + 'ɳ' => '伈', + 'ɴ' => '伝', + 'ɵ' => '伂', + 'ɶ' => '伅', + 'ɷ' => '伢', + 'ɸ' => '伓', + 'ɹ' => '伄', + 'ɺ' => '仴', + 'ɻ' => '伒', + 'ɼ' => '冱', + 'ɽ' => '刓', + 'ɾ' => '刉', + 'ɿ' => '刐', + '' => '劦', + '' => '匢', + '' => '匟', + '' => '卍', + '' => '厊', + '' => '吇', + '' => '囡', + '' => '囟', + '' => '圮', + '' => '圪', + '' => '圴', + '' => '夼', + '' => '妀', + '' => '奼', + '' => '妅', + '' => '奻', + '' => '奾', + '' => '奷', + '' => '奿', + '' => '孖', + '' => '尕', + '' => '尥', + '' => '屼', + '' => '屺', + '' => '屻', + '' => '屾', + '' => '巟', + '' => '幵', + '' => '庄', + '' => '异', + '' => '弚', + '' => '彴', + '' => '忕', + '' => '忔', + '' => '忏', + '' => '扜', + '' => '扞', + '' => '扤', + '' => '扡', + '' => '扦', + '' => '扢', + '' => '扙', + '' => '扠', + '' => '扚', + '' => '扥', + '' => '旯', + '' => '旮', + '' => '朾', + '' => '朹', + '' => '朸', + '' => '朻', + '' => '机', + '' => '朿', + '' => '朼', + '' => '朳', + '' => '氘', + '' => '汆', + '' => '汒', + '' => '汜', + '' => '汏', + '' => '汊', + '' => '汔', + '' => '汋', + '@' => '汌', + 'A' => '灱', + 'B' => '牞', + 'C' => '犴', + 'D' => '犵', + 'E' => '玎', + 'F' => '甪', + 'G' => '癿', + 'H' => '穵', + 'I' => '网', + 'J' => '艸', + 'K' => '艼', + 'L' => '芀', + 'M' => '艽', + 'N' => '艿', + 'O' => '虍', + 'P' => '襾', + 'Q' => '邙', + 'R' => '邗', + 'S' => '邘', + 'T' => '邛', + 'U' => '邔', + 'V' => '阢', + 'W' => '阤', + 'X' => '阠', + 'Y' => '阣', + 'Z' => '佖', + '[' => '伻', + '\\' => '佢', + ']' => '佉', + '^' => '体', + '_' => '佤', + '`' => '伾', + 'a' => '佧', + 'b' => '佒', + 'c' => '佟', + 'd' => '佁', + 'e' => '佘', + 'f' => '伭', + 'g' => '伳', + 'h' => '伿', + 'i' => '佡', + 'j' => '冏', + 'k' => '冹', + 'l' => '刜', + 'm' => '刞', + 'n' => '刡', + 'o' => '劭', + 'p' => '劮', + 'q' => '匉', + 'r' => '卣', + 's' => '卲', + 't' => '厎', + 'u' => '厏', + 'v' => '吰', + 'w' => '吷', + 'x' => '吪', + 'y' => '呔', + 'z' => '呅', + '{' => '吙', + '|' => '吜', + '}' => '吥', + '~' => '吘', + 'ʡ' => '吽', + 'ʢ' => '呏', + 'ʣ' => '呁', + 'ʤ' => '吨', + 'ʥ' => '吤', + 'ʦ' => '呇', + 'ʧ' => '囮', + 'ʨ' => '囧', + 'ʩ' => '囥', + 'ʪ' => '坁', + 'ʫ' => '坅', + 'ʬ' => '坌', + 'ʭ' => '坉', + 'ʮ' => '坋', + 'ʯ' => '坒', + 'ʰ' => '夆', + 'ʱ' => '奀', + 'ʲ' => '妦', + 'ʳ' => '妘', + 'ʴ' => '妠', + 'ʵ' => '妗', + 'ʶ' => '妎', + 'ʷ' => '妢', + 'ʸ' => '妐', + 'ʹ' => '妏', + 'ʺ' => '妧', + 'ʻ' => '妡', + 'ʼ' => '宎', + 'ʽ' => '宒', + 'ʾ' => '尨', + 'ʿ' => '尪', + '' => '岍', + '' => '岏', + '' => '岈', + '' => '岋', + '' => '岉', + '' => '岒', + '' => '岊', + '' => '岆', + '' => '岓', + '' => '岕', + '' => '巠', + '' => '帊', + '' => '帎', + '' => '庋', + '' => '庉', + '' => '庌', + '' => '庈', + '' => '庍', + '' => '弅', + '' => '弝', + '' => '彸', + '' => '彶', + '' => '忒', + '' => '忑', + '' => '忐', + '' => '忭', + '' => '忨', + '' => '忮', + '' => '忳', + '' => '忡', + '' => '忤', + '' => '忣', + '' => '忺', + '' => '忯', + '' => '忷', + '' => '忻', + '' => '怀', + '' => '忴', + '' => '戺', + '' => '抃', + '' => '抌', + '' => '抎', + '' => '抏', + '' => '抔', + '' => '抇', + '' => '扱', + '' => '扻', + '' => '扺', + '' => '扰', + '' => '抁', + '' => '抈', + '' => '扷', + '' => '扽', + '' => '扲', + '' => '扴', + '' => '攷', + '' => '旰', + '' => '旴', + '' => '旳', + '' => '旲', + '' => '旵', + '' => '杅', + '' => '杇', + '@' => '杙', + 'A' => '杕', + 'B' => '杌', + 'C' => '杈', + 'D' => '杝', + 'E' => '杍', + 'F' => '杚', + 'G' => '杋', + 'H' => '毐', + 'I' => '氙', + 'J' => '氚', + 'K' => '汸', + 'L' => '汧', + 'M' => '汫', + 'N' => '沄', + 'O' => '沋', + 'P' => '沏', + 'Q' => '汱', + 'R' => '汯', + 'S' => '汩', + 'T' => '沚', + 'U' => '汭', + 'V' => '沇', + 'W' => '沕', + 'X' => '沜', + 'Y' => '汦', + 'Z' => '汳', + '[' => '汥', + '\\' => '汻', + ']' => '沎', + '^' => '灴', + '_' => '灺', + '`' => '牣', + 'a' => '犿', + 'b' => '犽', + 'c' => '狃', + 'd' => '狆', + 'e' => '狁', + 'f' => '犺', + 'g' => '狅', + 'h' => '玕', + 'i' => '玗', + 'j' => '玓', + 'k' => '玔', + 'l' => '玒', + 'm' => '町', + 'n' => '甹', + 'o' => '疔', + 'p' => '疕', + 'q' => '皁', + 'r' => '礽', + 's' => '耴', + 't' => '肕', + 'u' => '肙', + 'v' => '肐', + 'w' => '肒', + 'x' => '肜', + 'y' => '芐', + 'z' => '芏', + '{' => '芅', + '|' => '芎', + '}' => '芑', + '~' => '芓', + 'ˡ' => '芊', + 'ˢ' => '芃', + 'ˣ' => '芄', + 'ˤ' => '豸', + '˥' => '迉', + '˦' => '辿', + '˧' => '邟', + '˨' => '邡', + '˩' => '邥', + '˪' => '邞', + '˫' => '邧', + 'ˬ' => '邠', + '˭' => '阰', + 'ˮ' => '阨', + '˯' => '阯', + '˰' => '阭', + '˱' => '丳', + '˲' => '侘', + '˳' => '佼', + '˴' => '侅', + '˵' => '佽', + '˶' => '侀', + '˷' => '侇', + '˸' => '佶', + '˹' => '佴', + '˺' => '侉', + '˻' => '侄', + '˼' => '佷', + '˽' => '佌', + '˾' => '侗', + '˿' => '佪', + '' => '侚', + '' => '佹', + '' => '侁', + '' => '佸', + '' => '侐', + '' => '侜', + '' => '侔', + '' => '侞', + '' => '侒', + '' => '侂', + '' => '侕', + '' => '佫', + '' => '佮', + '' => '冞', + '' => '冼', + '' => '冾', + '' => '刵', + '' => '刲', + '' => '刳', + '' => '剆', + '' => '刱', + '' => '劼', + '' => '匊', + '' => '匋', + '' => '匼', + '' => '厒', + '' => '厔', + '' => '咇', + '' => '呿', + '' => '咁', + '' => '咑', + '' => '咂', + '' => '咈', + '' => '呫', + '' => '呺', + '' => '呾', + '' => '呥', + '' => '呬', + '' => '呴', + '' => '呦', + '' => '咍', + '' => '呯', + '' => '呡', + '' => '呠', + '' => '咘', + '' => '呣', + '' => '呧', + '' => '呤', + '' => '囷', + '' => '囹', + '' => '坯', + '' => '坲', + '' => '坭', + '' => '坫', + '' => '坱', + '' => '坰', + '' => '坶', + '' => '垀', + '' => '坵', + '' => '坻', + '' => '坳', + '' => '坴', + '' => '坢', + '@' => '坨', + 'A' => '坽', + 'B' => '夌', + 'C' => '奅', + 'D' => '妵', + 'E' => '妺', + 'F' => '姏', + 'G' => '姎', + 'H' => '妲', + 'I' => '姌', + 'J' => '姁', + 'K' => '妶', + 'L' => '妼', + 'M' => '姃', + 'N' => '姖', + 'O' => '妱', + 'P' => '妽', + 'Q' => '姀', + 'R' => '姈', + 'S' => '妴', + 'T' => '姇', + 'U' => '孢', + 'V' => '孥', + 'W' => '宓', + 'X' => '宕', + 'Y' => '屄', + 'Z' => '屇', + '[' => '岮', + '\\' => '岤', + ']' => '岠', + '^' => '岵', + '_' => '岯', + '`' => '岨', + 'a' => '岬', + 'b' => '岟', + 'c' => '岣', + 'd' => '岭', + 'e' => '岢', + 'f' => '岪', + 'g' => '岧', + 'h' => '岝', + 'i' => '岥', + 'j' => '岶', + 'k' => '岰', + 'l' => '岦', + 'm' => '帗', + 'n' => '帔', + 'o' => '帙', + 'p' => '弨', + 'q' => '弢', + 'r' => '弣', + 's' => '弤', + 't' => '彔', + 'u' => '徂', + 'v' => '彾', + 'w' => '彽', + 'x' => '忞', + 'y' => '忥', + 'z' => '怭', + '{' => '怦', + '|' => '怙', + '}' => '怲', + '~' => '怋', + '̡' => '怴', + '̢' => '怊', + '̣' => '怗', + '̤' => '怳', + '̥' => '怚', + '̦' => '怞', + '̧' => '怬', + '̨' => '怢', + '̩' => '怍', + '̪' => '怐', + '̫' => '怮', + '̬' => '怓', + '̭' => '怑', + '̮' => '怌', + '̯' => '怉', + '̰' => '怜', + '̱' => '戔', + '̲' => '戽', + '̳' => '抭', + '̴' => '抴', + '̵' => '拑', + '̶' => '抾', + '̷' => '抪', + '̸' => '抶', + '̹' => '拊', + '̺' => '抮', + '̻' => '抳', + '̼' => '抯', + '̽' => '抻', + '̾' => '抩', + '̿' => '抰', + '' => '抸', + '' => '攽', + '' => '斨', + '' => '斻', + '' => '昉', + '' => '旼', + '' => '昄', + '' => '昒', + '' => '昈', + '' => '旻', + '' => '昃', + '' => '昋', + '' => '昍', + '' => '昅', + '' => '旽', + '' => '昑', + '' => '昐', + '' => '曶', + '' => '朊', + '' => '枅', + '' => '杬', + '' => '枎', + '' => '枒', + '' => '杶', + '' => '杻', + '' => '枘', + '' => '枆', + '' => '构', + '' => '杴', + '' => '枍', + '' => '枌', + '' => '杺', + '' => '枟', + '' => '枑', + '' => '枙', + '' => '枃', + '' => '杽', + '' => '极', + '' => '杸', + '' => '杹', + '' => '枔', + '' => '欥', + '' => '殀', + '' => '歾', + '' => '毞', + '' => '氝', + '' => '沓', + '' => '泬', + '' => '泫', + '' => '泮', + '' => '泙', + '' => '沶', + '' => '泔', + '' => '沭', + '' => '泧', + '' => '沷', + '' => '泐', + '' => '泂', + '' => '沺', + '' => '泃', + '' => '泆', + '' => '泭', + '' => '泲', + '@' => '泒', + 'A' => '泝', + 'B' => '沴', + 'C' => '沊', + 'D' => '沝', + 'E' => '沀', + 'F' => '泞', + 'G' => '泀', + 'H' => '洰', + 'I' => '泍', + 'J' => '泇', + 'K' => '沰', + 'L' => '泹', + 'M' => '泏', + 'N' => '泩', + 'O' => '泑', + 'P' => '炔', + 'Q' => '炘', + 'R' => '炅', + 'S' => '炓', + 'T' => '炆', + 'U' => '炄', + 'V' => '炑', + 'W' => '炖', + 'X' => '炂', + 'Y' => '炚', + 'Z' => '炃', + '[' => '牪', + '\\' => '狖', + ']' => '狋', + '^' => '狘', + '_' => '狉', + '`' => '狜', + 'a' => '狒', + 'b' => '狔', + 'c' => '狚', + 'd' => '狌', + 'e' => '狑', + 'f' => '玤', + 'g' => '玡', + 'h' => '玭', + 'i' => '玦', + 'j' => '玢', + 'k' => '玠', + 'l' => '玬', + 'm' => '玝', + 'n' => '瓝', + 'o' => '瓨', + 'p' => '甿', + 'q' => '畀', + 'r' => '甾', + 's' => '疌', + 't' => '疘', + 'u' => '皯', + 'v' => '盳', + 'w' => '盱', + 'x' => '盰', + 'y' => '盵', + 'z' => '矸', + '{' => '矼', + '|' => '矹', + '}' => '矻', + '~' => '矺', + '͡' => '矷', + '͢' => '祂', + 'ͣ' => '礿', + 'ͤ' => '秅', + 'ͥ' => '穸', + 'ͦ' => '穻', + 'ͧ' => '竻', + 'ͨ' => '籵', + 'ͩ' => '糽', + 'ͪ' => '耵', + 'ͫ' => '肏', + 'ͬ' => '肮', + 'ͭ' => '肣', + 'ͮ' => '肸', + 'ͯ' => '肵', + 'Ͱ' => '肭', + 'ͱ' => '舠', + 'Ͳ' => '芠', + 'ͳ' => '苀', + 'ʹ' => '芫', + '͵' => '芚', + 'Ͷ' => '芘', + 'ͷ' => '芛', + '͸' => '芵', + '͹' => '芧', + 'ͺ' => '芮', + 'ͻ' => '芼', + 'ͼ' => '芞', + 'ͽ' => '芺', + ';' => '芴', + 'Ϳ' => '芨', + '' => '芡', + '' => '芩', + '' => '苂', + '' => '芤', + '' => '苃', + '' => '芶', + '' => '芢', + '' => '虰', + '' => '虯', + '' => '虭', + '' => '虮', + '' => '豖', + '' => '迒', + '' => '迋', + '' => '迓', + '' => '迍', + '' => '迖', + '' => '迕', + '' => '迗', + '' => '邲', + '' => '邴', + '' => '邯', + '' => '邳', + '' => '邰', + '' => '阹', + '' => '阽', + '' => '阼', + '' => '阺', + '' => '陃', + '' => '俍', + '' => '俅', + '' => '俓', + '' => '侲', + '' => '俉', + '' => '俋', + '' => '俁', + '' => '俔', + '' => '俜', + '' => '俙', + '' => '侻', + '' => '侳', + '' => '俛', + '' => '俇', + '' => '俖', + '' => '侺', + '' => '俀', + '' => '侹', + '' => '俬', + '' => '剄', + '' => '剉', + '' => '勀', + '' => '勂', + '' => '匽', + '' => '卼', + '' => '厗', + '' => '厖', + '' => '厙', + '' => '厘', + '' => '咺', + '' => '咡', + '' => '咭', + '' => '咥', + '' => '哏', + '@' => '哃', + 'A' => '茍', + 'B' => '咷', + 'C' => '咮', + 'D' => '哖', + 'E' => '咶', + 'F' => '哅', + 'G' => '哆', + 'H' => '咠', + 'I' => '呰', + 'J' => '咼', + 'K' => '咢', + 'L' => '咾', + 'M' => '呲', + 'N' => '哞', + 'O' => '咰', + 'P' => '垵', + 'Q' => '垞', + 'R' => '垟', + 'S' => '垤', + 'T' => '垌', + 'U' => '垗', + 'V' => '垝', + 'W' => '垛', + 'X' => '垔', + 'Y' => '垘', + 'Z' => '垏', + '[' => '垙', + '\\' => '垥', + ']' => '垚', + '^' => '垕', + '_' => '壴', + '`' => '复', + 'a' => '奓', + 'b' => '姡', + 'c' => '姞', + 'd' => '姮', + 'e' => '娀', + 'f' => '姱', + 'g' => '姝', + 'h' => '姺', + 'i' => '姽', + 'j' => '姼', + 'k' => '姶', + 'l' => '姤', + 'm' => '姲', + 'n' => '姷', + 'o' => '姛', + 'p' => '姩', + 'q' => '姳', + 'r' => '姵', + 's' => '姠', + 't' => '姾', + 'u' => '姴', + 'v' => '姭', + 'w' => '宨', + 'x' => '屌', + 'y' => '峐', + 'z' => '峘', + '{' => '峌', + '|' => '峗', + '}' => '峋', + '~' => '峛', + 'Ρ' => '峞', + '΢' => '峚', + 'Σ' => '峉', + 'Τ' => '峇', + 'Υ' => '峊', + 'Φ' => '峖', + 'Χ' => '峓', + 'Ψ' => '峔', + 'Ω' => '峏', + 'Ϊ' => '峈', + 'Ϋ' => '峆', + 'ά' => '峎', + 'έ' => '峟', + 'ή' => '峸', + 'ί' => '巹', + 'ΰ' => '帡', + 'α' => '帢', + 'β' => '帣', + 'γ' => '帠', + 'δ' => '帤', + 'ε' => '庰', + 'ζ' => '庤', + 'η' => '庢', + 'θ' => '庛', + 'ι' => '庣', + 'κ' => '庥', + 'λ' => '弇', + 'μ' => '弮', + 'ν' => '彖', + 'ξ' => '徆', + 'ο' => '怷', + '' => '怹', + '' => '恔', + '' => '恲', + '' => '恞', + '' => '恅', + '' => '恓', + '' => '恇', + '' => '恉', + '' => '恛', + '' => '恌', + '' => '恀', + '' => '恂', + '' => '恟', + '' => '怤', + '' => '恄', + '' => '恘', + '' => '恦', + '' => '恮', + '' => '扂', + '' => '扃', + '' => '拏', + '' => '挍', + '' => '挋', + '' => '拵', + '' => '挎', + '' => '挃', + '' => '拫', + '' => '拹', + '' => '挏', + '' => '挌', + '' => '拸', + '' => '拶', + '' => '挀', + '' => '挓', + '' => '挔', + '' => '拺', + '' => '挕', + '' => '拻', + '' => '拰', + '' => '敁', + '' => '敃', + '' => '斪', + '' => '斿', + '' => '昶', + '' => '昡', + '' => '昲', + '' => '昵', + '' => '昜', + '' => '昦', + '' => '昢', + '' => '昳', + '' => '昫', + '' => '昺', + '' => '昝', + '' => '昴', + '' => '昹', + '' => '昮', + '' => '朏', + '' => '朐', + '' => '柁', + '' => '柲', + '' => '柈', + '' => '枺', + '@' => '柜', + 'A' => '枻', + 'B' => '柸', + 'C' => '柘', + 'D' => '柀', + 'E' => '枷', + 'F' => '柅', + 'G' => '柫', + 'H' => '柤', + 'I' => '柟', + 'J' => '枵', + 'K' => '柍', + 'L' => '枳', + 'M' => '柷', + 'N' => '柶', + 'O' => '柮', + 'P' => '柣', + 'Q' => '柂', + 'R' => '枹', + 'S' => '柎', + 'T' => '柧', + 'U' => '柰', + 'V' => '枲', + 'W' => '柼', + 'X' => '柆', + 'Y' => '柭', + 'Z' => '柌', + '[' => '枮', + '\\' => '柦', + ']' => '柛', + '^' => '柺', + '_' => '柉', + '`' => '柊', + 'a' => '柃', + 'b' => '柪', + 'c' => '柋', + 'd' => '欨', + 'e' => '殂', + 'f' => '殄', + 'g' => '殶', + 'h' => '毖', + 'i' => '毘', + 'j' => '毠', + 'k' => '氠', + 'l' => '氡', + 'm' => '洨', + 'n' => '洴', + 'o' => '洭', + 'p' => '洟', + 'q' => '洼', + 'r' => '洿', + 's' => '洒', + 't' => '洊', + 'u' => '泚', + 'v' => '洳', + 'w' => '洄', + 'x' => '洙', + 'y' => '洺', + 'z' => '洚', + '{' => '洑', + '|' => '洀', + '}' => '洝', + '~' => '浂', + 'ϡ' => '洁', + 'Ϣ' => '洘', + 'ϣ' => '洷', + 'Ϥ' => '洃', + 'ϥ' => '洏', + 'Ϧ' => '浀', + 'ϧ' => '洇', + 'Ϩ' => '洠', + 'ϩ' => '洬', + 'Ϫ' => '洈', + 'ϫ' => '洢', + 'Ϭ' => '洉', + 'ϭ' => '洐', + 'Ϯ' => '炷', + 'ϯ' => '炟', + 'ϰ' => '炾', + 'ϱ' => '炱', + 'ϲ' => '炰', + 'ϳ' => '炡', + 'ϴ' => '炴', + 'ϵ' => '炵', + '϶' => '炩', + 'Ϸ' => '牁', + 'ϸ' => '牉', + 'Ϲ' => '牊', + 'Ϻ' => '牬', + 'ϻ' => '牰', + 'ϼ' => '牳', + 'Ͻ' => '牮', + 'Ͼ' => '狊', + 'Ͽ' => '狤', + '' => '狨', + '' => '狫', + '' => '狟', + '' => '狪', + '' => '狦', + '' => '狣', + '' => '玅', + '' => '珌', + '' => '珂', + '' => '珈', + '' => '珅', + '' => '玹', + '' => '玶', + '' => '玵', + '' => '玴', + '' => '珫', + '' => '玿', + '' => '珇', + '' => '玾', + '' => '珃', + '' => '珆', + '' => '玸', + '' => '珋', + '' => '瓬', + '' => '瓮', + '' => '甮', + '' => '畇', + '' => '畈', + '' => '疧', + '' => '疪', + '' => '癹', + '' => '盄', + '' => '眈', + '' => '眃', + '' => '眄', + '' => '眅', + '' => '眊', + '' => '盷', + '' => '盻', + '' => '盺', + '' => '矧', + '' => '矨', + '' => '砆', + '' => '砑', + '' => '砒', + '' => '砅', + '' => '砐', + '' => '砏', + '' => '砎', + '' => '砉', + '' => '砃', + '' => '砓', + '' => '祊', + '' => '祌', + '' => '祋', + '' => '祅', + '' => '祄', + '' => '秕', + '' => '种', + '' => '秏', + '' => '秖', + '' => '秎', + '' => '窀', + '@' => '穾', + 'A' => '竑', + 'B' => '笀', + 'C' => '笁', + 'D' => '籺', + 'E' => '籸', + 'F' => '籹', + 'G' => '籿', + 'H' => '粀', + 'I' => '粁', + 'J' => '紃', + 'K' => '紈', + 'L' => '紁', + 'M' => '罘', + 'N' => '羑', + 'O' => '羍', + 'P' => '羾', + 'Q' => '耇', + 'R' => '耎', + 'S' => '耏', + 'T' => '耔', + 'U' => '耷', + 'V' => '胘', + 'W' => '胇', + 'X' => '胠', + 'Y' => '胑', + 'Z' => '胈', + '[' => '胂', + '\\' => '胐', + ']' => '胅', + '^' => '胣', + '_' => '胙', + '`' => '胜', + 'a' => '胊', + 'b' => '胕', + 'c' => '胉', + 'd' => '胏', + 'e' => '胗', + 'f' => '胦', + 'g' => '胍', + 'h' => '臿', + 'i' => '舡', + 'j' => '芔', + 'k' => '苙', + 'l' => '苾', + 'm' => '苹', + 'n' => '茇', + 'o' => '苨', + 'p' => '茀', + 'q' => '苕', + 'r' => '茺', + 's' => '苫', + 't' => '苖', + 'u' => '苴', + 'v' => '苬', + 'w' => '苡', + 'x' => '苲', + 'y' => '苵', + 'z' => '茌', + '{' => '苻', + '|' => '苶', + '}' => '苰', + '~' => '苪', + 'С' => '苤', + 'Т' => '苠', + 'У' => '苺', + 'Ф' => '苳', + 'Х' => '苭', + 'Ц' => '虷', + 'Ч' => '虴', + 'Ш' => '虼', + 'Щ' => '虳', + 'Ъ' => '衁', + 'Ы' => '衎', + 'Ь' => '衧', + 'Э' => '衪', + 'Ю' => '衩', + 'Я' => '觓', + 'а' => '訄', + 'б' => '訇', + 'в' => '赲', + 'г' => '迣', + 'д' => '迡', + 'е' => '迮', + 'ж' => '迠', + 'з' => '郱', + 'и' => '邽', + 'й' => '邿', + 'к' => '郕', + 'л' => '郅', + 'м' => '邾', + 'н' => '郇', + 'о' => '郋', + 'п' => '郈', + '' => '釔', + '' => '釓', + '' => '陔', + '' => '陏', + '' => '陑', + '' => '陓', + '' => '陊', + '' => '陎', + '' => '倞', + '' => '倅', + '' => '倇', + '' => '倓', + '' => '倢', + '' => '倰', + '' => '倛', + '' => '俵', + '' => '俴', + '' => '倳', + '' => '倷', + '' => '倬', + '' => '俶', + '' => '俷', + '' => '倗', + '' => '倜', + '' => '倠', + '' => '倧', + '' => '倵', + '' => '倯', + '' => '倱', + '' => '倎', + '' => '党', + '' => '冔', + '' => '冓', + '' => '凊', + '' => '凄', + '' => '凅', + '' => '凈', + '' => '凎', + '' => '剡', + '' => '剚', + '' => '剒', + '' => '剞', + '' => '剟', + '' => '剕', + '' => '剢', + '' => '勍', + '' => '匎', + '' => '厞', + '' => '唦', + '' => '哢', + '' => '唗', + '' => '唒', + '' => '哧', + '' => '哳', + '' => '哤', + '' => '唚', + '' => '哿', + '' => '唄', + '' => '唈', + '' => '哫', + '' => '唑', + '' => '唅', + '' => '哱', + '@' => '唊', + 'A' => '哻', + 'B' => '哷', + 'C' => '哸', + 'D' => '哠', + 'E' => '唎', + 'F' => '唃', + 'G' => '唋', + 'H' => '圁', + 'I' => '圂', + 'J' => '埌', + 'K' => '堲', + 'L' => '埕', + 'M' => '埒', + 'N' => '垺', + 'O' => '埆', + 'P' => '垽', + 'Q' => '垼', + 'R' => '垸', + 'S' => '垶', + 'T' => '垿', + 'U' => '埇', + 'V' => '埐', + 'W' => '垹', + 'X' => '埁', + 'Y' => '夎', + 'Z' => '奊', + '[' => '娙', + '\\' => '娖', + ']' => '娭', + '^' => '娮', + '_' => '娕', + '`' => '娏', + 'a' => '娗', + 'b' => '娊', + 'c' => '娞', + 'd' => '娳', + 'e' => '孬', + 'f' => '宧', + 'g' => '宭', + 'h' => '宬', + 'i' => '尃', + 'j' => '屖', + 'k' => '屔', + 'l' => '峬', + 'm' => '峿', + 'n' => '峮', + 'o' => '峱', + 'p' => '峷', + 'q' => '崀', + 'r' => '峹', + 's' => '帩', + 't' => '帨', + 'u' => '庨', + 'v' => '庮', + 'w' => '庪', + 'x' => '庬', + 'y' => '弳', + 'z' => '弰', + '{' => '彧', + '|' => '恝', + '}' => '恚', + '~' => '恧', + 'ѡ' => '恁', + 'Ѣ' => '悢', + 'ѣ' => '悈', + 'Ѥ' => '悀', + 'ѥ' => '悒', + 'Ѧ' => '悁', + 'ѧ' => '悝', + 'Ѩ' => '悃', + 'ѩ' => '悕', + 'Ѫ' => '悛', + 'ѫ' => '悗', + 'Ѭ' => '悇', + 'ѭ' => '悜', + 'Ѯ' => '悎', + 'ѯ' => '戙', + 'Ѱ' => '扆', + 'ѱ' => '拲', + 'Ѳ' => '挐', + 'ѳ' => '捖', + 'Ѵ' => '挬', + 'ѵ' => '捄', + 'Ѷ' => '捅', + 'ѷ' => '挶', + 'Ѹ' => '捃', + 'ѹ' => '揤', + 'Ѻ' => '挹', + 'ѻ' => '捋', + 'Ѽ' => '捊', + 'ѽ' => '挼', + 'Ѿ' => '挩', + 'ѿ' => '捁', + '' => '挴', + '' => '捘', + '' => '捔', + '' => '捙', + '' => '挭', + '' => '捇', + '' => '挳', + '' => '捚', + '' => '捑', + '' => '挸', + '' => '捗', + '' => '捀', + '' => '捈', + '' => '敊', + '' => '敆', + '' => '旆', + '' => '旃', + '' => '旄', + '' => '旂', + '' => '晊', + '' => '晟', + '' => '晇', + '' => '晑', + '' => '朒', + '' => '朓', + '' => '栟', + '' => '栚', + '' => '桉', + '' => '栲', + '' => '栳', + '' => '栻', + '' => '桋', + '' => '桏', + '' => '栖', + '' => '栱', + '' => '栜', + '' => '栵', + '' => '栫', + '' => '栭', + '' => '栯', + '' => '桎', + '' => '桄', + '' => '栴', + '' => '栝', + '' => '栒', + '' => '栔', + '' => '栦', + '' => '栨', + '' => '栮', + '' => '桍', + '' => '栺', + '' => '栥', + '' => '栠', + '' => '欬', + '' => '欯', + '' => '欭', + '' => '欱', + '' => '欴', + '' => '歭', + '' => '肂', + '' => '殈', + '' => '毦', + '' => '毤', + '@' => '毨', + 'A' => '毣', + 'B' => '毢', + 'C' => '毧', + 'D' => '氥', + 'E' => '浺', + 'F' => '浣', + 'G' => '浤', + 'H' => '浶', + 'I' => '洍', + 'J' => '浡', + 'K' => '涒', + 'L' => '浘', + 'M' => '浢', + 'N' => '浭', + 'O' => '浯', + 'P' => '涑', + 'Q' => '涍', + 'R' => '淯', + 'S' => '浿', + 'T' => '涆', + 'U' => '浞', + 'V' => '浧', + 'W' => '浠', + 'X' => '涗', + 'Y' => '浰', + 'Z' => '浼', + '[' => '浟', + '\\' => '涂', + ']' => '涘', + '^' => '洯', + '_' => '浨', + '`' => '涋', + 'a' => '浾', + 'b' => '涀', + 'c' => '涄', + 'd' => '洖', + 'e' => '涃', + 'f' => '浻', + 'g' => '浽', + 'h' => '浵', + 'i' => '涐', + 'j' => '烜', + 'k' => '烓', + 'l' => '烑', + 'm' => '烝', + 'n' => '烋', + 'o' => '缹', + 'p' => '烢', + 'q' => '烗', + 'r' => '烒', + 's' => '烞', + 't' => '烠', + 'u' => '烔', + 'v' => '烍', + 'w' => '烅', + 'x' => '烆', + 'y' => '烇', + 'z' => '烚', + '{' => '烎', + '|' => '烡', + '}' => '牂', + '~' => '牸', + 'ҡ' => '牷', + 'Ң' => '牶', + 'ң' => '猀', + 'Ҥ' => '狺', + 'ҥ' => '狴', + 'Ҧ' => '狾', + 'ҧ' => '狶', + 'Ҩ' => '狳', + 'ҩ' => '狻', + 'Ҫ' => '猁', + 'ҫ' => '珓', + 'Ҭ' => '珙', + 'ҭ' => '珥', + 'Ү' => '珖', + 'ү' => '玼', + 'Ұ' => '珧', + 'ұ' => '珣', + 'Ҳ' => '珩', + 'ҳ' => '珜', + 'Ҵ' => '珒', + 'ҵ' => '珛', + 'Ҷ' => '珔', + 'ҷ' => '珝', + 'Ҹ' => '珚', + 'ҹ' => '珗', + 'Һ' => '珘', + 'һ' => '珨', + 'Ҽ' => '瓞', + 'ҽ' => '瓟', + 'Ҿ' => '瓴', + 'ҿ' => '瓵', + '' => '甡', + '' => '畛', + '' => '畟', + '' => '疰', + '' => '痁', + '' => '疻', + '' => '痄', + '' => '痀', + '' => '疿', + '' => '疶', + '' => '疺', + '' => '皊', + '' => '盉', + '' => '眝', + '' => '眛', + '' => '眐', + '' => '眓', + '' => '眒', + '' => '眣', + '' => '眑', + '' => '眕', + '' => '眙', + '' => '眚', + '' => '眢', + '' => '眧', + '' => '砣', + '' => '砬', + '' => '砢', + '' => '砵', + '' => '砯', + '' => '砨', + '' => '砮', + '' => '砫', + '' => '砡', + '' => '砩', + '' => '砳', + '' => '砪', + '' => '砱', + '' => '祔', + '' => '祛', + '' => '祏', + '' => '祜', + '' => '祓', + '' => '祒', + '' => '祑', + '' => '秫', + '' => '秬', + '' => '秠', + '' => '秮', + '' => '秭', + '' => '秪', + '' => '秜', + '' => '秞', + '' => '秝', + '' => '窆', + '' => '窉', + '' => '窅', + '' => '窋', + '' => '窌', + '' => '窊', + '' => '窇', + '' => '竘', + '' => '笐', + '@' => '笄', + 'A' => '笓', + 'B' => '笅', + 'C' => '笏', + 'D' => '笈', + 'E' => '笊', + 'F' => '笎', + 'G' => '笉', + 'H' => '笒', + 'I' => '粄', + 'J' => '粑', + 'K' => '粊', + 'L' => '粌', + 'M' => '粈', + 'N' => '粍', + 'O' => '粅', + 'P' => '紞', + 'Q' => '紝', + 'R' => '紑', + 'S' => '紎', + 'T' => '紘', + 'U' => '紖', + 'V' => '紓', + 'W' => '紟', + 'X' => '紒', + 'Y' => '紏', + 'Z' => '紌', + '[' => '罜', + '\\' => '罡', + ']' => '罞', + '^' => '罠', + '_' => '罝', + '`' => '罛', + 'a' => '羖', + 'b' => '羒', + 'c' => '翃', + 'd' => '翂', + 'e' => '翀', + 'f' => '耖', + 'g' => '耾', + 'h' => '耹', + 'i' => '胺', + 'j' => '胲', + 'k' => '胹', + 'l' => '胵', + 'm' => '脁', + 'n' => '胻', + 'o' => '脀', + 'p' => '舁', + 'q' => '舯', + 'r' => '舥', + 's' => '茳', + 't' => '茭', + 'u' => '荄', + 'v' => '茙', + 'w' => '荑', + 'x' => '茥', + 'y' => '荖', + 'z' => '茿', + '{' => '荁', + '|' => '茦', + '}' => '茜', + '~' => '茢', + 'ӡ' => '荂', + 'Ӣ' => '荎', + 'ӣ' => '茛', + 'Ӥ' => '茪', + 'ӥ' => '茈', + 'Ӧ' => '茼', + 'ӧ' => '荍', + 'Ө' => '茖', + 'ө' => '茤', + 'Ӫ' => '茠', + 'ӫ' => '茷', + 'Ӭ' => '茯', + 'ӭ' => '茩', + 'Ӯ' => '荇', + 'ӯ' => '荅', + 'Ӱ' => '荌', + 'ӱ' => '荓', + 'Ӳ' => '茞', + 'ӳ' => '茬', + 'Ӵ' => '荋', + 'ӵ' => '茧', + 'Ӷ' => '荈', + 'ӷ' => '虓', + 'Ӹ' => '虒', + 'ӹ' => '蚢', + 'Ӻ' => '蚨', + 'ӻ' => '蚖', + 'Ӽ' => '蚍', + 'ӽ' => '蚑', + 'Ӿ' => '蚞', + 'ӿ' => '蚇', + '' => '蚗', + '' => '蚆', + '' => '蚋', + '' => '蚚', + '' => '蚅', + '' => '蚥', + '' => '蚙', + '' => '蚡', + '' => '蚧', + '' => '蚕', + '' => '蚘', + '' => '蚎', + '' => '蚝', + '' => '蚐', + '' => '蚔', + '' => '衃', + '' => '衄', + '' => '衭', + '' => '衵', + '' => '衶', + '' => '衲', + '' => '袀', + '' => '衱', + '' => '衿', + '' => '衯', + '' => '袃', + '' => '衾', + '' => '衴', + '' => '衼', + '' => '訒', + '' => '豇', + '' => '豗', + '' => '豻', + '' => '貤', + '' => '貣', + '' => '赶', + '' => '赸', + '' => '趵', + '' => '趷', + '' => '趶', + '' => '軑', + '' => '軓', + '' => '迾', + '' => '迵', + '' => '适', + '' => '迿', + '' => '迻', + '' => '逄', + '' => '迼', + '' => '迶', + '' => '郖', + '' => '郠', + '' => '郙', + '' => '郚', + '' => '郣', + '' => '郟', + '' => '郥', + '' => '郘', + '' => '郛', + '' => '郗', + '' => '郜', + '' => '郤', + '' => '酐', + '@' => '酎', + 'A' => '酏', + 'B' => '釕', + 'C' => '釢', + 'D' => '釚', + 'E' => '陜', + 'F' => '陟', + 'G' => '隼', + 'H' => '飣', + 'I' => '髟', + 'J' => '鬯', + 'K' => '乿', + 'L' => '偰', + 'M' => '偪', + 'N' => '偡', + 'O' => '偞', + 'P' => '偠', + 'Q' => '偓', + 'R' => '偋', + 'S' => '偝', + 'T' => '偲', + 'U' => '偈', + 'V' => '偍', + 'W' => '偁', + 'X' => '偛', + 'Y' => '偊', + 'Z' => '偢', + '[' => '倕', + '\\' => '偅', + ']' => '偟', + '^' => '偩', + '_' => '偫', + '`' => '偣', + 'a' => '偤', + 'b' => '偆', + 'c' => '偀', + 'd' => '偮', + 'e' => '偳', + 'f' => '偗', + 'g' => '偑', + 'h' => '凐', + 'i' => '剫', + 'j' => '剭', + 'k' => '剬', + 'l' => '剮', + 'm' => '勖', + 'n' => '勓', + 'o' => '匭', + 'p' => '厜', + 'q' => '啵', + 'r' => '啶', + 's' => '唼', + 't' => '啍', + 'u' => '啐', + 'v' => '唴', + 'w' => '唪', + 'x' => '啑', + 'y' => '啢', + 'z' => '唶', + '{' => '唵', + '|' => '唰', + '}' => '啒', + '~' => '啅', + 'ԡ' => '唌', + 'Ԣ' => '唲', + 'ԣ' => '啥', + 'Ԥ' => '啎', + 'ԥ' => '唹', + 'Ԧ' => '啈', + 'ԧ' => '唭', + 'Ԩ' => '唻', + 'ԩ' => '啀', + 'Ԫ' => '啋', + 'ԫ' => '圊', + 'Ԭ' => '圇', + 'ԭ' => '埻', + 'Ԯ' => '堔', + 'ԯ' => '埢', + '԰' => '埶', + 'Ա' => '埜', + 'Բ' => '埴', + 'Գ' => '堀', + 'Դ' => '埭', + 'Ե' => '埽', + 'Զ' => '堈', + 'Է' => '埸', + 'Ը' => '堋', + 'Թ' => '埳', + 'Ժ' => '埏', + 'Ի' => '堇', + 'Լ' => '埮', + 'Խ' => '埣', + 'Ծ' => '埲', + 'Կ' => '埥', + '' => '埬', + '' => '埡', + '' => '堎', + '' => '埼', + '' => '堐', + '' => '埧', + '' => '堁', + '' => '堌', + '' => '埱', + '' => '埩', + '' => '埰', + '' => '堍', + '' => '堄', + '' => '奜', + '' => '婠', + '' => '婘', + '' => '婕', + '' => '婧', + '' => '婞', + '' => '娸', + '' => '娵', + '' => '婭', + '' => '婐', + '' => '婟', + '' => '婥', + '' => '婬', + '' => '婓', + '' => '婤', + '' => '婗', + '' => '婃', + '' => '婝', + '' => '婒', + '' => '婄', + '' => '婛', + '' => '婈', + '' => '媎', + '' => '娾', + '' => '婍', + '' => '娹', + '' => '婌', + '' => '婰', + '' => '婩', + '' => '婇', + '' => '婑', + '' => '婖', + '' => '婂', + '' => '婜', + '' => '孲', + '' => '孮', + '' => '寁', + '' => '寀', + '' => '屙', + '' => '崞', + '' => '崋', + '' => '崝', + '' => '崚', + '' => '崠', + '' => '崌', + '' => '崨', + '' => '崍', + '' => '崦', + '' => '崥', + '' => '崏', + '@' => '崰', + 'A' => '崒', + 'B' => '崣', + 'C' => '崟', + 'D' => '崮', + 'E' => '帾', + 'F' => '帴', + 'G' => '庱', + 'H' => '庴', + 'I' => '庹', + 'J' => '庲', + 'K' => '庳', + 'L' => '弶', + 'M' => '弸', + 'N' => '徛', + 'O' => '徖', + 'P' => '徟', + 'Q' => '悊', + 'R' => '悐', + 'S' => '悆', + 'T' => '悾', + 'U' => '悰', + 'V' => '悺', + 'W' => '惓', + 'X' => '惔', + 'Y' => '惏', + 'Z' => '惤', + '[' => '惙', + '\\' => '惝', + ']' => '惈', + '^' => '悱', + '_' => '惛', + '`' => '悷', + 'a' => '惊', + 'b' => '悿', + 'c' => '惃', + 'd' => '惍', + 'e' => '惀', + 'f' => '挲', + 'g' => '捥', + 'h' => '掊', + 'i' => '掂', + 'j' => '捽', + 'k' => '掽', + 'l' => '掞', + 'm' => '掭', + 'n' => '掝', + 'o' => '掗', + 'p' => '掫', + 'q' => '掎', + 'r' => '捯', + 's' => '掇', + 't' => '掐', + 'u' => '据', + 'v' => '掯', + 'w' => '捵', + 'x' => '掜', + 'y' => '捭', + 'z' => '掮', + '{' => '捼', + '|' => '掤', + '}' => '挻', + '~' => '掟', + 'ա' => '捸', + 'բ' => '掅', + 'գ' => '掁', + 'դ' => '掑', + 'ե' => '掍', + 'զ' => '捰', + 'է' => '敓', + 'ը' => '旍', + 'թ' => '晥', + 'ժ' => '晡', + 'ի' => '晛', + 'լ' => '晙', + 'խ' => '晜', + 'ծ' => '晢', + 'կ' => '朘', + 'հ' => '桹', + 'ձ' => '梇', + 'ղ' => '梐', + 'ճ' => '梜', + 'մ' => '桭', + 'յ' => '桮', + 'ն' => '梮', + 'շ' => '梫', + 'ո' => '楖', + 'չ' => '桯', + 'պ' => '梣', + 'ջ' => '梬', + 'ռ' => '梩', + 'ս' => '桵', + 'վ' => '桴', + 'տ' => '梲', + '' => '梏', + '' => '桷', + '' => '梒', + '' => '桼', + '' => '桫', + '' => '桲', + '' => '梪', + '' => '梀', + '' => '桱', + '' => '桾', + '' => '梛', + '' => '梖', + '' => '梋', + '' => '梠', + '' => '梉', + '' => '梤', + '' => '桸', + '' => '桻', + '' => '梑', + '' => '梌', + '' => '梊', + '' => '桽', + '' => '欶', + '' => '欳', + '' => '欷', + '' => '欸', + '' => '殑', + '' => '殏', + '' => '殍', + '' => '殎', + '' => '殌', + '' => '氪', + '' => '淀', + '' => '涫', + '' => '涴', + '' => '涳', + '' => '湴', + '' => '涬', + '' => '淩', + '' => '淢', + '' => '涷', + '' => '淶', + '' => '淔', + '' => '渀', + '' => '淈', + '' => '淠', + '' => '淟', + '' => '淖', + '' => '涾', + '' => '淥', + '' => '淜', + '' => '淝', + '' => '淛', + '' => '淴', + '' => '淊', + '' => '涽', + '' => '淭', + '' => '淰', + '' => '涺', + '' => '淕', + '' => '淂', + '' => '淏', + '' => '淉', + '@' => '淐', + 'A' => '淲', + 'B' => '淓', + 'C' => '淽', + 'D' => '淗', + 'E' => '淍', + 'F' => '淣', + 'G' => '涻', + 'H' => '烺', + 'I' => '焍', + 'J' => '烷', + 'K' => '焗', + 'L' => '烴', + 'M' => '焌', + 'N' => '烰', + 'O' => '焄', + 'P' => '烳', + 'Q' => '焐', + 'R' => '烼', + 'S' => '烿', + 'T' => '焆', + 'U' => '焓', + 'V' => '焀', + 'W' => '烸', + 'X' => '烶', + 'Y' => '焋', + 'Z' => '焂', + '[' => '焎', + '\\' => '牾', + ']' => '牻', + '^' => '牼', + '_' => '牿', + '`' => '猝', + 'a' => '猗', + 'b' => '猇', + 'c' => '猑', + 'd' => '猘', + 'e' => '猊', + 'f' => '猈', + 'g' => '狿', + 'h' => '猏', + 'i' => '猞', + 'j' => '玈', + 'k' => '珶', + 'l' => '珸', + 'm' => '珵', + 'n' => '琄', + 'o' => '琁', + 'p' => '珽', + 'q' => '琇', + 'r' => '琀', + 's' => '珺', + 't' => '珼', + 'u' => '珿', + 'v' => '琌', + 'w' => '琋', + 'x' => '珴', + 'y' => '琈', + 'z' => '畤', + '{' => '畣', + '|' => '痎', + '}' => '痒', + '~' => '痏', + '֡' => '痋', + '֢' => '痌', + '֣' => '痑', + '֤' => '痐', + '֥' => '皏', + '֦' => '皉', + '֧' => '盓', + '֨' => '眹', + '֩' => '眯', + '֪' => '眭', + '֫' => '眱', + '֬' => '眲', + '֭' => '眴', + '֮' => '眳', + '֯' => '眽', + 'ְ' => '眥', + 'ֱ' => '眻', + 'ֲ' => '眵', + 'ֳ' => '硈', + 'ִ' => '硒', + 'ֵ' => '硉', + 'ֶ' => '硍', + 'ַ' => '硊', + 'ָ' => '硌', + 'ֹ' => '砦', + 'ֺ' => '硅', + 'ֻ' => '硐', + 'ּ' => '祤', + 'ֽ' => '祧', + '־' => '祩', + 'ֿ' => '祪', + '' => '祣', + '' => '祫', + '' => '祡', + '' => '离', + '' => '秺', + '' => '秸', + '' => '秶', + '' => '秷', + '' => '窏', + '' => '窔', + '' => '窐', + '' => '笵', + '' => '筇', + '' => '笴', + '' => '笥', + '' => '笰', + '' => '笢', + '' => '笤', + '' => '笳', + '' => '笘', + '' => '笪', + '' => '笝', + '' => '笱', + '' => '笫', + '' => '笭', + '' => '笯', + '' => '笲', + '' => '笸', + '' => '笚', + '' => '笣', + '' => '粔', + '' => '粘', + '' => '粖', + '' => '粣', + '' => '紵', + '' => '紽', + '' => '紸', + '' => '紶', + '' => '紺', + '' => '絅', + '' => '紬', + '' => '紩', + '' => '絁', + '' => '絇', + '' => '紾', + '' => '紿', + '' => '絊', + '' => '紻', + '' => '紨', + '' => '罣', + '' => '羕', + '' => '羜', + '' => '羝', + '' => '羛', + '' => '翊', + '' => '翋', + '' => '翍', + '' => '翐', + '' => '翑', + '' => '翇', + '' => '翏', + '' => '翉', + '' => '耟', + '@' => '耞', + 'A' => '耛', + 'B' => '聇', + 'C' => '聃', + 'D' => '聈', + 'E' => '脘', + 'F' => '脥', + 'G' => '脙', + 'H' => '脛', + 'I' => '脭', + 'J' => '脟', + 'K' => '脬', + 'L' => '脞', + 'M' => '脡', + 'N' => '脕', + 'O' => '脧', + 'P' => '脝', + 'Q' => '脢', + 'R' => '舑', + 'S' => '舸', + 'T' => '舳', + 'U' => '舺', + 'V' => '舴', + 'W' => '舲', + 'X' => '艴', + 'Y' => '莐', + 'Z' => '莣', + '[' => '莨', + '\\' => '莍', + ']' => '荺', + '^' => '荳', + '_' => '莤', + '`' => '荴', + 'a' => '莏', + 'b' => '莁', + 'c' => '莕', + 'd' => '莙', + 'e' => '荵', + 'f' => '莔', + 'g' => '莩', + 'h' => '荽', + 'i' => '莃', + 'j' => '莌', + 'k' => '莝', + 'l' => '莛', + 'm' => '莪', + 'n' => '莋', + 'o' => '荾', + 'p' => '莥', + 'q' => '莯', + 'r' => '莈', + 's' => '莗', + 't' => '莰', + 'u' => '荿', + 'v' => '莦', + 'w' => '莇', + 'x' => '莮', + 'y' => '荶', + 'z' => '莚', + '{' => '虙', + '|' => '虖', + '}' => '蚿', + '~' => '蚷', + 'ס' => '蛂', + 'ע' => '蛁', + 'ף' => '蛅', + 'פ' => '蚺', + 'ץ' => '蚰', + 'צ' => '蛈', + 'ק' => '蚹', + 'ר' => '蚳', + 'ש' => '蚸', + 'ת' => '蛌', + '׫' => '蚴', + '׬' => '蚻', + '׭' => '蚼', + '׮' => '蛃', + 'ׯ' => '蚽', + 'װ' => '蚾', + 'ױ' => '衒', + 'ײ' => '袉', + '׳' => '袕', + '״' => '袨', + '׵' => '袢', + '׶' => '袪', + '׷' => '袚', + '׸' => '袑', + '׹' => '袡', + '׺' => '袟', + '׻' => '袘', + '׼' => '袧', + '׽' => '袙', + '׾' => '袛', + '׿' => '袗', + '' => '袤', + '' => '袬', + '' => '袌', + '' => '袓', + '' => '袎', + '' => '覂', + '' => '觖', + '' => '觙', + '' => '觕', + '' => '訰', + '' => '訧', + '' => '訬', + '' => '訞', + '' => '谹', + '' => '谻', + '' => '豜', + '' => '豝', + '' => '豽', + '' => '貥', + '' => '赽', + '' => '赻', + '' => '赹', + '' => '趼', + '' => '跂', + '' => '趹', + '' => '趿', + '' => '跁', + '' => '軘', + '' => '軞', + '' => '軝', + '' => '軜', + '' => '軗', + '' => '軠', + '' => '軡', + '' => '逤', + '' => '逋', + '' => '逑', + '' => '逜', + '' => '逌', + '' => '逡', + '' => '郯', + '' => '郪', + '' => '郰', + '' => '郴', + '' => '郲', + '' => '郳', + '' => '郔', + '' => '郫', + '' => '郬', + '' => '郩', + '' => '酖', + '' => '酘', + '' => '酚', + '' => '酓', + '' => '酕', + '' => '釬', + '' => '釴', + '' => '釱', + '' => '釳', + '' => '釸', + '' => '釤', + '' => '釹', + '' => '釪', + '@' => '釫', + 'A' => '釷', + 'B' => '釨', + 'C' => '釮', + 'D' => '镺', + 'E' => '閆', + 'F' => '閈', + 'G' => '陼', + 'H' => '陭', + 'I' => '陫', + 'J' => '陱', + 'K' => '陯', + 'L' => '隿', + 'M' => '靪', + 'N' => '頄', + 'O' => '飥', + 'P' => '馗', + 'Q' => '傛', + 'R' => '傕', + 'S' => '傔', + 'T' => '傞', + 'U' => '傋', + 'V' => '傣', + 'W' => '傃', + 'X' => '傌', + 'Y' => '傎', + 'Z' => '傝', + '[' => '偨', + '\\' => '傜', + ']' => '傒', + '^' => '傂', + '_' => '傇', + '`' => '兟', + 'a' => '凔', + 'b' => '匒', + 'c' => '匑', + 'd' => '厤', + 'e' => '厧', + 'f' => '喑', + 'g' => '喨', + 'h' => '喥', + 'i' => '喭', + 'j' => '啷', + 'k' => '噅', + 'l' => '喢', + 'm' => '喓', + 'n' => '喈', + 'o' => '喏', + 'p' => '喵', + 'q' => '喁', + 'r' => '喣', + 's' => '喒', + 't' => '喤', + 'u' => '啽', + 'v' => '喌', + 'w' => '喦', + 'x' => '啿', + 'y' => '喕', + 'z' => '喡', + '{' => '喎', + '|' => '圌', + '}' => '堩', + '~' => '堷', + 'ء' => '堙', + 'آ' => '堞', + 'أ' => '堧', + 'ؤ' => '堣', + 'إ' => '堨', + 'ئ' => '埵', + 'ا' => '塈', + 'ب' => '堥', + 'ة' => '堜', + 'ت' => '堛', + 'ث' => '堳', + 'ج' => '堿', + 'ح' => '堶', + 'خ' => '堮', + 'د' => '堹', + 'ذ' => '堸', + 'ر' => '堭', + 'ز' => '堬', + 'س' => '堻', + 'ش' => '奡', + 'ص' => '媯', + 'ض' => '媔', + 'ط' => '媟', + 'ظ' => '婺', + 'ع' => '媢', + 'غ' => '媞', + 'ػ' => '婸', + 'ؼ' => '媦', + 'ؽ' => '婼', + 'ؾ' => '媥', + 'ؿ' => '媬', + '' => '媕', + '' => '媮', + '' => '娷', + '' => '媄', + '' => '媊', + '' => '媗', + '' => '媃', + '' => '媋', + '' => '媩', + '' => '婻', + '' => '婽', + '' => '媌', + '' => '媜', + '' => '媏', + '' => '媓', + '' => '媝', + '' => '寪', + '' => '寍', + '' => '寋', + '' => '寔', + '' => '寑', + '' => '寊', + '' => '寎', + '' => '尌', + '' => '尰', + '' => '崷', + '' => '嵃', + '' => '嵫', + '' => '嵁', + '' => '嵋', + '' => '崿', + '' => '崵', + '' => '嵑', + '' => '嵎', + '' => '嵕', + '' => '崳', + '' => '崺', + '' => '嵒', + '' => '崽', + '' => '崱', + '' => '嵙', + '' => '嵂', + '' => '崹', + '' => '嵉', + '' => '崸', + '' => '崼', + '' => '崲', + '' => '崶', + '' => '嵀', + '' => '嵅', + '' => '幄', + '' => '幁', + '' => '彘', + '' => '徦', + '' => '徥', + '' => '徫', + '' => '惉', + '' => '悹', + '' => '惌', + '' => '惢', + '' => '惎', + '' => '惄', + '' => '愔', + '@' => '惲', + 'A' => '愊', + 'B' => '愖', + 'C' => '愅', + 'D' => '惵', + 'E' => '愓', + 'F' => '惸', + 'G' => '惼', + 'H' => '惾', + 'I' => '惁', + 'J' => '愃', + 'K' => '愘', + 'L' => '愝', + 'M' => '愐', + 'N' => '惿', + 'O' => '愄', + 'P' => '愋', + 'Q' => '扊', + 'R' => '掔', + 'S' => '掱', + 'T' => '掰', + 'U' => '揎', + 'V' => '揥', + 'W' => '揨', + 'X' => '揯', + 'Y' => '揃', + 'Z' => '撝', + '[' => '揳', + '\\' => '揊', + ']' => '揠', + '^' => '揶', + '_' => '揕', + '`' => '揲', + 'a' => '揵', + 'b' => '摡', + 'c' => '揟', + 'd' => '掾', + 'e' => '揝', + 'f' => '揜', + 'g' => '揄', + 'h' => '揘', + 'i' => '揓', + 'j' => '揂', + 'k' => '揇', + 'l' => '揌', + 'm' => '揋', + 'n' => '揈', + 'o' => '揰', + 'p' => '揗', + 'q' => '揙', + 'r' => '攲', + 's' => '敧', + 't' => '敪', + 'u' => '敤', + 'v' => '敜', + 'w' => '敨', + 'x' => '敥', + 'y' => '斌', + 'z' => '斝', + '{' => '斞', + '|' => '斮', + '}' => '旐', + '~' => '旒', + '١' => '晼', + '٢' => '晬', + '٣' => '晻', + '٤' => '暀', + '٥' => '晱', + '٦' => '晹', + '٧' => '晪', + '٨' => '晲', + '٩' => '朁', + '٪' => '椌', + '٫' => '棓', + '٬' => '椄', + '٭' => '棜', + 'ٮ' => '椪', + 'ٯ' => '棬', + 'ٰ' => '棪', + 'ٱ' => '棱', + 'ٲ' => '椏', + 'ٳ' => '棖', + 'ٴ' => '棷', + 'ٵ' => '棫', + 'ٶ' => '棤', + 'ٷ' => '棶', + 'ٸ' => '椓', + 'ٹ' => '椐', + 'ٺ' => '棳', + 'ٻ' => '棡', + 'ټ' => '椇', + 'ٽ' => '棌', + 'پ' => '椈', + 'ٿ' => '楰', + '' => '梴', + '' => '椑', + '' => '棯', + '' => '棆', + '' => '椔', + '' => '棸', + '' => '棐', + '' => '棽', + '' => '棼', + '' => '棨', + '' => '椋', + '' => '椊', + '' => '椗', + '' => '棎', + '' => '棈', + '' => '棝', + '' => '棞', + '' => '棦', + '' => '棴', + '' => '棑', + '' => '椆', + '' => '棔', + '' => '棩', + '' => '椕', + '' => '椥', + '' => '棇', + '' => '欹', + '' => '欻', + '' => '欿', + '' => '欼', + '' => '殔', + '' => '殗', + '' => '殙', + '' => '殕', + '' => '殽', + '' => '毰', + '' => '毲', + '' => '毳', + '' => '氰', + '' => '淼', + '' => '湆', + '' => '湇', + '' => '渟', + '' => '湉', + '' => '溈', + '' => '渼', + '' => '渽', + '' => '湅', + '' => '湢', + '' => '渫', + '' => '渿', + '' => '湁', + '' => '湝', + '' => '湳', + '' => '渜', + '' => '渳', + '' => '湋', + '' => '湀', + '' => '湑', + '' => '渻', + '' => '渃', + '' => '渮', + '' => '湞', + '@' => '湨', + 'A' => '湜', + 'B' => '湡', + 'C' => '渱', + 'D' => '渨', + 'E' => '湠', + 'F' => '湱', + 'G' => '湫', + 'H' => '渹', + 'I' => '渢', + 'J' => '渰', + 'K' => '湓', + 'L' => '湥', + 'M' => '渧', + 'N' => '湸', + 'O' => '湤', + 'P' => '湷', + 'Q' => '湕', + 'R' => '湹', + 'S' => '湒', + 'T' => '湦', + 'U' => '渵', + 'V' => '渶', + 'W' => '湚', + 'X' => '焠', + 'Y' => '焞', + 'Z' => '焯', + '[' => '烻', + '\\' => '焮', + ']' => '焱', + '^' => '焣', + '_' => '焥', + '`' => '焢', + 'a' => '焲', + 'b' => '焟', + 'c' => '焨', + 'd' => '焺', + 'e' => '焛', + 'f' => '牋', + 'g' => '牚', + 'h' => '犈', + 'i' => '犉', + 'j' => '犆', + 'k' => '犅', + 'l' => '犋', + 'm' => '猒', + 'n' => '猋', + 'o' => '猰', + 'p' => '猢', + 'q' => '猱', + 'r' => '猳', + 's' => '猧', + 't' => '猲', + 'u' => '猭', + 'v' => '猦', + 'w' => '猣', + 'x' => '猵', + 'y' => '猌', + 'z' => '琮', + '{' => '琬', + '|' => '琰', + '}' => '琫', + '~' => '琖', + 'ڡ' => '琚', + 'ڢ' => '琡', + 'ڣ' => '琭', + 'ڤ' => '琱', + 'ڥ' => '琤', + 'ڦ' => '琣', + 'ڧ' => '琝', + 'ڨ' => '琩', + 'ک' => '琠', + 'ڪ' => '琲', + 'ګ' => '瓻', + 'ڬ' => '甯', + 'ڭ' => '畯', + 'ڮ' => '畬', + 'گ' => '痧', + 'ڰ' => '痚', + 'ڱ' => '痡', + 'ڲ' => '痦', + 'ڳ' => '痝', + 'ڴ' => '痟', + 'ڵ' => '痤', + 'ڶ' => '痗', + 'ڷ' => '皕', + 'ڸ' => '皒', + 'ڹ' => '盚', + 'ں' => '睆', + 'ڻ' => '睇', + 'ڼ' => '睄', + 'ڽ' => '睍', + 'ھ' => '睅', + 'ڿ' => '睊', + '' => '睎', + '' => '睋', + '' => '睌', + '' => '矞', + '' => '矬', + '' => '硠', + '' => '硤', + '' => '硥', + '' => '硜', + '' => '硭', + '' => '硱', + '' => '硪', + '' => '确', + '' => '硰', + '' => '硩', + '' => '硨', + '' => '硞', + '' => '硢', + '' => '祴', + '' => '祳', + '' => '祲', + '' => '祰', + '' => '稂', + '' => '稊', + '' => '稃', + '' => '稌', + '' => '稄', + '' => '窙', + '' => '竦', + '' => '竤', + '' => '筊', + '' => '笻', + '' => '筄', + '' => '筈', + '' => '筌', + '' => '筎', + '' => '筀', + '' => '筘', + '' => '筅', + '' => '粢', + '' => '粞', + '' => '粨', + '' => '粡', + '' => '絘', + '' => '絯', + '' => '絣', + '' => '絓', + '' => '絖', + '' => '絧', + '' => '絪', + '' => '絏', + '' => '絭', + '' => '絜', + '' => '絫', + '' => '絒', + '' => '絔', + '' => '絩', + '' => '絑', + '' => '絟', + '' => '絎', + '' => '缾', + '' => '缿', + '' => '罥', + '@' => '罦', + 'A' => '羢', + 'B' => '羠', + 'C' => '羡', + 'D' => '翗', + 'E' => '聑', + 'F' => '聏', + 'G' => '聐', + 'H' => '胾', + 'I' => '胔', + 'J' => '腃', + 'K' => '腊', + 'L' => '腒', + 'M' => '腏', + 'N' => '腇', + 'O' => '脽', + 'P' => '腍', + 'Q' => '脺', + 'R' => '臦', + 'S' => '臮', + 'T' => '臷', + 'U' => '臸', + 'V' => '臹', + 'W' => '舄', + 'X' => '舼', + 'Y' => '舽', + 'Z' => '舿', + '[' => '艵', + '\\' => '茻', + ']' => '菏', + '^' => '菹', + '_' => '萣', + '`' => '菀', + 'a' => '菨', + 'b' => '萒', + 'c' => '菧', + 'd' => '菤', + 'e' => '菼', + 'f' => '菶', + 'g' => '萐', + 'h' => '菆', + 'i' => '菈', + 'j' => '菫', + 'k' => '菣', + 'l' => '莿', + 'm' => '萁', + 'n' => '菝', + 'o' => '菥', + 'p' => '菘', + 'q' => '菿', + 'r' => '菡', + 's' => '菋', + 't' => '菎', + 'u' => '菖', + 'v' => '菵', + 'w' => '菉', + 'x' => '萉', + 'y' => '萏', + 'z' => '菞', + '{' => '萑', + '|' => '萆', + '}' => '菂', + '~' => '菳', + 'ۡ' => '菕', + 'ۢ' => '菺', + 'ۣ' => '菇', + 'ۤ' => '菑', + 'ۥ' => '菪', + 'ۦ' => '萓', + 'ۧ' => '菃', + 'ۨ' => '菬', + '۩' => '菮', + '۪' => '菄', + '۫' => '菻', + '۬' => '菗', + 'ۭ' => '菢', + 'ۮ' => '萛', + 'ۯ' => '菛', + '۰' => '菾', + '۱' => '蛘', + '۲' => '蛢', + '۳' => '蛦', + '۴' => '蛓', + '۵' => '蛣', + '۶' => '蛚', + '۷' => '蛪', + '۸' => '蛝', + '۹' => '蛫', + 'ۺ' => '蛜', + 'ۻ' => '蛬', + 'ۼ' => '蛩', + '۽' => '蛗', + '۾' => '蛨', + 'ۿ' => '蛑', + '' => '衈', + '' => '衖', + '' => '衕', + '' => '袺', + '' => '裗', + '' => '袹', + '' => '袸', + '' => '裀', + '' => '袾', + '' => '袶', + '' => '袼', + '' => '袷', + '' => '袽', + '' => '袲', + '' => '褁', + '' => '裉', + '' => '覕', + '' => '覘', + '' => '覗', + '' => '觝', + '' => '觚', + '' => '觛', + '' => '詎', + '' => '詍', + '' => '訹', + '' => '詙', + '' => '詀', + '' => '詗', + '' => '詘', + '' => '詄', + '' => '詅', + '' => '詒', + '' => '詈', + '' => '詑', + '' => '詊', + '' => '詌', + '' => '詏', + '' => '豟', + '' => '貁', + '' => '貀', + '' => '貺', + '' => '貾', + '' => '貰', + '' => '貹', + '' => '貵', + '' => '趄', + '' => '趀', + '' => '趉', + '' => '跘', + '' => '跓', + '' => '跍', + '' => '跇', + '' => '跖', + '' => '跜', + '' => '跏', + '' => '跕', + '' => '跙', + '' => '跈', + '' => '跗', + '' => '跅', + '' => '軯', + '' => '軷', + '' => '軺', + '@' => '軹', + 'A' => '軦', + 'B' => '軮', + 'C' => '軥', + 'D' => '軵', + 'E' => '軧', + 'F' => '軨', + 'G' => '軶', + 'H' => '軫', + 'I' => '軱', + 'J' => '軬', + 'K' => '軴', + 'L' => '軩', + 'M' => '逭', + 'N' => '逴', + 'O' => '逯', + 'P' => '鄆', + 'Q' => '鄬', + 'R' => '鄄', + 'S' => '郿', + 'T' => '郼', + 'U' => '鄈', + 'V' => '郹', + 'W' => '郻', + 'X' => '鄁', + 'Y' => '鄀', + 'Z' => '鄇', + '[' => '鄅', + '\\' => '鄃', + ']' => '酡', + '^' => '酤', + '_' => '酟', + '`' => '酢', + 'a' => '酠', + 'b' => '鈁', + 'c' => '鈊', + 'd' => '鈥', + 'e' => '鈃', + 'f' => '鈚', + 'g' => '鈦', + 'h' => '鈏', + 'i' => '鈌', + 'j' => '鈀', + 'k' => '鈒', + 'l' => '釿', + 'm' => '釽', + 'n' => '鈆', + 'o' => '鈄', + 'p' => '鈧', + 'q' => '鈂', + 'r' => '鈜', + 's' => '鈤', + 't' => '鈙', + 'u' => '鈗', + 'v' => '鈅', + 'w' => '鈖', + 'x' => '镻', + 'y' => '閍', + 'z' => '閌', + '{' => '閐', + '|' => '隇', + '}' => '陾', + '~' => '隈', + 'ܡ' => '隉', + 'ܢ' => '隃', + 'ܣ' => '隀', + 'ܤ' => '雂', + 'ܥ' => '雈', + 'ܦ' => '雃', + 'ܧ' => '雱', + 'ܨ' => '雰', + 'ܩ' => '靬', + 'ܪ' => '靰', + 'ܫ' => '靮', + 'ܬ' => '頇', + 'ܭ' => '颩', + 'ܮ' => '飫', + 'ܯ' => '鳦', + 'ܰ' => '黹', + 'ܱ' => '亃', + 'ܲ' => '亄', + 'ܳ' => '亶', + 'ܴ' => '傽', + 'ܵ' => '傿', + 'ܶ' => '僆', + 'ܷ' => '傮', + 'ܸ' => '僄', + 'ܹ' => '僊', + 'ܺ' => '傴', + 'ܻ' => '僈', + 'ܼ' => '僂', + 'ܽ' => '傰', + 'ܾ' => '僁', + 'ܿ' => '傺', + '' => '傱', + '' => '僋', + '' => '僉', + '' => '傶', + '' => '傸', + '' => '凗', + '' => '剺', + '' => '剸', + '' => '剻', + '' => '剼', + '' => '嗃', + '' => '嗛', + '' => '嗌', + '' => '嗐', + '' => '嗋', + '' => '嗊', + '' => '嗝', + '' => '嗀', + '' => '嗔', + '' => '嗄', + '' => '嗩', + '' => '喿', + '' => '嗒', + '' => '喍', + '' => '嗏', + '' => '嗕', + '' => '嗢', + '' => '嗖', + '' => '嗈', + '' => '嗲', + '' => '嗍', + '' => '嗙', + '' => '嗂', + '' => '圔', + '' => '塓', + '' => '塨', + '' => '塤', + '' => '塏', + '' => '塍', + '' => '塉', + '' => '塯', + '' => '塕', + '' => '塎', + '' => '塝', + '' => '塙', + '' => '塥', + '' => '塛', + '' => '堽', + '' => '塣', + '' => '塱', + '' => '壼', + '' => '嫇', + '' => '嫄', + '' => '嫋', + '' => '媺', + '' => '媸', + '' => '媱', + '' => '媵', + '' => '媰', + '' => '媿', + '' => '嫈', + '' => '媻', + '' => '嫆', + '@' => '媷', + 'A' => '嫀', + 'B' => '嫊', + 'C' => '媴', + 'D' => '媶', + 'E' => '嫍', + 'F' => '媹', + 'G' => '媐', + 'H' => '寖', + 'I' => '寘', + 'J' => '寙', + 'K' => '尟', + 'L' => '尳', + 'M' => '嵱', + 'N' => '嵣', + 'O' => '嵊', + 'P' => '嵥', + 'Q' => '嵲', + 'R' => '嵬', + 'S' => '嵞', + 'T' => '嵨', + 'U' => '嵧', + 'V' => '嵢', + 'W' => '巰', + 'X' => '幏', + 'Y' => '幎', + 'Z' => '幊', + '[' => '幍', + '\\' => '幋', + ']' => '廅', + '^' => '廌', + '_' => '廆', + '`' => '廋', + 'a' => '廇', + 'b' => '彀', + 'c' => '徯', + 'd' => '徭', + 'e' => '惷', + 'f' => '慉', + 'g' => '慊', + 'h' => '愫', + 'i' => '慅', + 'j' => '愶', + 'k' => '愲', + 'l' => '愮', + 'm' => '慆', + 'n' => '愯', + 'o' => '慏', + 'p' => '愩', + 'q' => '慀', + 'r' => '戠', + 's' => '酨', + 't' => '戣', + 'u' => '戥', + 'v' => '戤', + 'w' => '揅', + 'x' => '揱', + 'y' => '揫', + 'z' => '搐', + '{' => '搒', + '|' => '搉', + '}' => '搠', + '~' => '搤', + 'ݡ' => '搳', + 'ݢ' => '摃', + 'ݣ' => '搟', + 'ݤ' => '搕', + 'ݥ' => '搘', + 'ݦ' => '搹', + 'ݧ' => '搷', + 'ݨ' => '搢', + 'ݩ' => '搣', + 'ݪ' => '搌', + 'ݫ' => '搦', + 'ݬ' => '搰', + 'ݭ' => '搨', + 'ݮ' => '摁', + 'ݯ' => '搵', + 'ݰ' => '搯', + 'ݱ' => '搊', + 'ݲ' => '搚', + 'ݳ' => '摀', + 'ݴ' => '搥', + 'ݵ' => '搧', + 'ݶ' => '搋', + 'ݷ' => '揧', + 'ݸ' => '搛', + 'ݹ' => '搮', + 'ݺ' => '搡', + 'ݻ' => '搎', + 'ݼ' => '敯', + 'ݽ' => '斒', + 'ݾ' => '旓', + 'ݿ' => '暆', + '' => '暌', + '' => '暕', + '' => '暐', + '' => '暋', + '' => '暊', + '' => '暙', + '' => '暔', + '' => '晸', + '' => '朠', + '' => '楦', + '' => '楟', + '' => '椸', + '' => '楎', + '' => '楢', + '' => '楱', + '' => '椿', + '' => '楅', + '' => '楪', + '' => '椹', + '' => '楂', + '' => '楗', + '' => '楙', + '' => '楺', + '' => '楈', + '' => '楉', + '' => '椵', + '' => '楬', + '' => '椳', + '' => '椽', + '' => '楥', + '' => '棰', + '' => '楸', + '' => '椴', + '' => '楩', + '' => '楀', + '' => '楯', + '' => '楄', + '' => '楶', + '' => '楘', + '' => '楁', + '' => '楴', + '' => '楌', + '' => '椻', + '' => '楋', + '' => '椷', + '' => '楜', + '' => '楏', + '' => '楑', + '' => '椲', + '' => '楒', + '' => '椯', + '' => '楻', + '' => '椼', + '' => '歆', + '' => '歅', + '' => '歃', + '' => '歂', + '' => '歈', + '' => '歁', + '' => '殛', + '' => '嗀', + '' => '毻', + '' => '毼', + '@' => '毹', + 'A' => '毷', + 'B' => '毸', + 'C' => '溛', + 'D' => '滖', + 'E' => '滈', + 'F' => '溏', + 'G' => '滀', + 'H' => '溟', + 'I' => '溓', + 'J' => '溔', + 'K' => '溠', + 'L' => '溱', + 'M' => '溹', + 'N' => '滆', + 'O' => '滒', + 'P' => '溽', + 'Q' => '滁', + 'R' => '溞', + 'S' => '滉', + 'T' => '溷', + 'U' => '溰', + 'V' => '滍', + 'W' => '溦', + 'X' => '滏', + 'Y' => '溲', + 'Z' => '溾', + '[' => '滃', + '\\' => '滜', + ']' => '滘', + '^' => '溙', + '_' => '溒', + '`' => '溎', + 'a' => '溍', + 'b' => '溤', + 'c' => '溡', + 'd' => '溿', + 'e' => '溳', + 'f' => '滐', + 'g' => '滊', + 'h' => '溗', + 'i' => '溮', + 'j' => '溣', + 'k' => '煇', + 'l' => '煔', + 'm' => '煒', + 'n' => '煣', + 'o' => '煠', + 'p' => '煁', + 'q' => '煝', + 'r' => '煢', + 's' => '煲', + 't' => '煸', + 'u' => '煪', + 'v' => '煡', + 'w' => '煂', + 'x' => '煘', + 'y' => '煃', + 'z' => '煋', + '{' => '煰', + '|' => '煟', + '}' => '煐', + '~' => '煓', + 'ޡ' => '煄', + 'ޢ' => '煍', + 'ޣ' => '煚', + 'ޤ' => '牏', + 'ޥ' => '犍', + 'ަ' => '犌', + 'ާ' => '犑', + 'ި' => '犐', + 'ީ' => '犎', + 'ު' => '猼', + 'ޫ' => '獂', + 'ެ' => '猻', + 'ޭ' => '猺', + 'ޮ' => '獀', + 'ޯ' => '獊', + 'ް' => '獉', + 'ޱ' => '瑄', + '޲' => '瑊', + '޳' => '瑋', + '޴' => '瑒', + '޵' => '瑑', + '޶' => '瑗', + '޷' => '瑀', + '޸' => '瑏', + '޹' => '瑐', + '޺' => '瑎', + '޻' => '瑂', + '޼' => '瑆', + '޽' => '瑍', + '޾' => '瑔', + '޿' => '瓡', + '' => '瓿', + '' => '瓾', + '' => '瓽', + '' => '甝', + '' => '畹', + '' => '畷', + '' => '榃', + '' => '痯', + '' => '瘏', + '' => '瘃', + '' => '痷', + '' => '痾', + '' => '痼', + '' => '痹', + '' => '痸', + '' => '瘐', + '' => '痻', + '' => '痶', + '' => '痭', + '' => '痵', + '' => '痽', + '' => '皙', + '' => '皵', + '' => '盝', + '' => '睕', + '' => '睟', + '' => '睠', + '' => '睒', + '' => '睖', + '' => '睚', + '' => '睩', + '' => '睧', + '' => '睔', + '' => '睙', + '' => '睭', + '' => '矠', + '' => '碇', + '' => '碚', + '' => '碔', + '' => '碏', + '' => '碄', + '' => '碕', + '' => '碅', + '' => '碆', + '' => '碡', + '' => '碃', + '' => '硹', + '' => '碙', + '' => '碀', + '' => '碖', + '' => '硻', + '' => '祼', + '' => '禂', + '' => '祽', + '' => '祹', + '' => '稑', + '' => '稘', + '' => '稙', + '' => '稒', + '' => '稗', + '' => '稕', + '' => '稢', + '' => '稓', + '@' => '稛', + 'A' => '稐', + 'B' => '窣', + 'C' => '窢', + 'D' => '窞', + 'E' => '竫', + 'F' => '筦', + 'G' => '筤', + 'H' => '筭', + 'I' => '筴', + 'J' => '筩', + 'K' => '筲', + 'L' => '筥', + 'M' => '筳', + 'N' => '筱', + 'O' => '筰', + 'P' => '筡', + 'Q' => '筸', + 'R' => '筶', + 'S' => '筣', + 'T' => '粲', + 'U' => '粴', + 'V' => '粯', + 'W' => '綈', + 'X' => '綆', + 'Y' => '綀', + 'Z' => '綍', + '[' => '絿', + '\\' => '綅', + ']' => '絺', + '^' => '綎', + '_' => '絻', + '`' => '綃', + 'a' => '絼', + 'b' => '綌', + 'c' => '綔', + 'd' => '綄', + 'e' => '絽', + 'f' => '綒', + 'g' => '罭', + 'h' => '罫', + 'i' => '罧', + 'j' => '罨', + 'k' => '罬', + 'l' => '羦', + 'm' => '羥', + 'n' => '羧', + 'o' => '翛', + 'p' => '翜', + 'q' => '耡', + 'r' => '腤', + 's' => '腠', + 't' => '腷', + 'u' => '腜', + 'v' => '腩', + 'w' => '腛', + 'x' => '腢', + 'y' => '腲', + 'z' => '朡', + '{' => '腞', + '|' => '腶', + '}' => '腧', + '~' => '腯', + 'ߡ' => '腄', + 'ߢ' => '腡', + 'ߣ' => '舝', + 'ߤ' => '艉', + 'ߥ' => '艄', + 'ߦ' => '艀', + 'ߧ' => '艂', + 'ߨ' => '艅', + 'ߩ' => '蓱', + 'ߪ' => '萿', + '߫' => '葖', + '߬' => '葶', + '߭' => '葹', + '߮' => '蒏', + '߯' => '蒍', + '߰' => '葥', + '߱' => '葑', + '߲' => '葀', + '߳' => '蒆', + 'ߴ' => '葧', + 'ߵ' => '萰', + '߶' => '葍', + '߷' => '葽', + '߸' => '葚', + '߹' => '葙', + 'ߺ' => '葴', + '߻' => '葳', + '߼' => '葝', + '߽' => '蔇', + '߾' => '葞', + '߿' => '萷', + '' => '萺', + '' => '萴', + '' => '葺', + '' => '葃', + '' => '葸', + '' => '萲', + '' => '葅', + '' => '萩', + '' => '菙', + '' => '葋', + '' => '萯', + '' => '葂', + '' => '萭', + '' => '葟', + '' => '葰', + '' => '萹', + '' => '葎', + '' => '葌', + '' => '葒', + '' => '葯', + '' => '蓅', + '' => '蒎', + '' => '萻', + '' => '葇', + '' => '萶', + '' => '萳', + '' => '葨', + '' => '葾', + '' => '葄', + '' => '萫', + '' => '葠', + '' => '葔', + '' => '葮', + '' => '葐', + '' => '蜋', + '' => '蜄', + '' => '蛷', + '' => '蜌', + '' => '蛺', + '' => '蛖', + '' => '蛵', + '' => '蝍', + '' => '蛸', + '' => '蜎', + '' => '蜉', + '' => '蜁', + '' => '蛶', + '' => '蜍', + '' => '蜅', + '' => '裖', + '' => '裋', + '' => '裍', + '' => '裎', + '' => '裞', + '' => '裛', + '' => '裚', + '' => '裌', + '' => '裐', + '' => '覅', + '' => '覛', + '' => '觟', + '' => '觥', + '' => '觤', + '@' => '觡', + 'A' => '觠', + 'B' => '觢', + 'C' => '觜', + 'D' => '触', + 'E' => '詶', + 'F' => '誆', + 'G' => '詿', + 'H' => '詡', + 'I' => '訿', + 'J' => '詷', + 'K' => '誂', + 'L' => '誄', + 'M' => '詵', + 'N' => '誃', + 'O' => '誁', + 'P' => '詴', + 'Q' => '詺', + 'R' => '谼', + 'S' => '豋', + 'T' => '豊', + 'U' => '豥', + 'V' => '豤', + 'W' => '豦', + 'X' => '貆', + 'Y' => '貄', + 'Z' => '貅', + '[' => '賌', + '\\' => '赨', + ']' => '赩', + '^' => '趑', + '_' => '趌', + '`' => '趎', + 'a' => '趏', + 'b' => '趍', + 'c' => '趓', + 'd' => '趔', + 'e' => '趐', + 'f' => '趒', + 'g' => '跰', + 'h' => '跠', + 'i' => '跬', + 'j' => '跱', + 'k' => '跮', + 'l' => '跐', + 'm' => '跩', + 'n' => '跣', + 'o' => '跢', + 'p' => '跧', + 'q' => '跲', + 'r' => '跫', + 's' => '跴', + 't' => '輆', + 'u' => '軿', + 'v' => '輁', + 'w' => '輀', + 'x' => '輅', + 'y' => '輇', + 'z' => '輈', + '{' => '輂', + '|' => '輋', + '}' => '遒', + '~' => '逿', + '' => '遄', + '' => '遉', + '' => '逽', + '' => '鄐', + '' => '鄍', + '' => '鄏', + '' => '鄑', + '' => '鄖', + '' => '鄔', + '' => '鄋', + '' => '鄎', + '' => '酮', + '' => '酯', + '' => '鉈', + '' => '鉒', + '' => '鈰', + '' => '鈺', + '' => '鉦', + '' => '鈳', + '' => '鉥', + '' => '鉞', + '' => '銃', + '' => '鈮', + '' => '鉊', + '' => '鉆', + '' => '鉭', + '' => '鉬', + '' => '鉏', + '' => '鉠', + '' => '鉧', + '' => '鉯', + '' => '鈶', + '' => '鉡', + '' => '鉰', + '' => '鈱', + '' => '鉔', + '' => '鉣', + '' => '鉐', + '' => '鉲', + '' => '鉎', + '' => '鉓', + '' => '鉌', + '' => '鉖', + '' => '鈲', + '' => '閟', + '' => '閜', + '' => '閞', + '' => '閛', + '' => '隒', + '' => '隓', + '' => '隑', + '' => '隗', + '' => '雎', + '' => '雺', + '' => '雽', + '' => '雸', + '' => '雵', + '' => '靳', + '' => '靷', + '' => '靸', + '' => '靲', + '' => '頏', + '' => '頍', + '' => '頎', + '' => '颬', + '' => '飶', + '' => '飹', + '' => '馯', + '' => '馲', + '' => '馰', + '' => '馵', + '' => '骭', + '' => '骫', + '' => '魛', + '' => '鳪', + '' => '鳭', + '' => '鳧', + '' => '麀', + '' => '黽', + '' => '僦', + '' => '僔', + '' => '僗', + '' => '僨', + '' => '僳', + '' => '僛', + '' => '僪', + '' => '僝', + '' => '僤', + '' => '僓', + '' => '僬', + '' => '僰', + '' => '僯', + '' => '僣', + '' => '僠', + '@' => '凘', + 'A' => '劀', + 'B' => '劁', + 'C' => '勩', + 'D' => '勫', + 'E' => '匰', + 'F' => '厬', + 'G' => '嘧', + 'H' => '嘕', + 'I' => '嘌', + 'J' => '嘒', + 'K' => '嗼', + 'L' => '嘏', + 'M' => '嘜', + 'N' => '嘁', + 'O' => '嘓', + 'P' => '嘂', + 'Q' => '嗺', + 'R' => '嘝', + 'S' => '嘄', + 'T' => '嗿', + 'U' => '嗹', + 'V' => '墉', + 'W' => '塼', + 'X' => '墐', + 'Y' => '墘', + 'Z' => '墆', + '[' => '墁', + '\\' => '塿', + ']' => '塴', + '^' => '墋', + '_' => '塺', + '`' => '墇', + 'a' => '墑', + 'b' => '墎', + 'c' => '塶', + 'd' => '墂', + 'e' => '墈', + 'f' => '塻', + 'g' => '墔', + 'h' => '墏', + 'i' => '壾', + 'j' => '奫', + 'k' => '嫜', + 'l' => '嫮', + 'm' => '嫥', + 'n' => '嫕', + 'o' => '嫪', + 'p' => '嫚', + 'q' => '嫭', + 'r' => '嫫', + 's' => '嫳', + 't' => '嫢', + 'u' => '嫠', + 'v' => '嫛', + 'w' => '嫬', + 'x' => '嫞', + 'y' => '嫝', + 'z' => '嫙', + '{' => '嫨', + '|' => '嫟', + '}' => '孷', + '~' => '寠', + '' => '寣', + '' => '屣', + '' => '嶂', + '' => '嶀', + '' => '嵽', + '' => '嶆', + '' => '嵺', + '' => '嶁', + '' => '嵷', + '' => '嶊', + '' => '嶉', + '' => '嶈', + '' => '嵾', + '' => '嵼', + '' => '嶍', + '' => '嵹', + '' => '嵿', + '' => '幘', + '' => '幙', + '' => '幓', + '' => '廘', + '' => '廑', + '' => '廗', + '' => '廎', + '' => '廜', + '' => '廕', + '' => '廙', + '' => '廒', + '' => '廔', + '' => '彄', + '' => '彃', + '' => '彯', + '' => '徶', + '' => '愬', + '' => '愨', + '' => '慁', + '' => '慞', + '' => '慱', + '' => '慳', + '' => '慒', + '' => '慓', + '' => '慲', + '' => '慬', + '' => '憀', + '' => '慴', + '' => '慔', + '' => '慺', + '' => '慛', + '' => '慥', + '' => '愻', + '' => '慪', + '' => '慡', + '' => '慖', + '' => '戩', + '' => '戧', + '' => '戫', + '' => '搫', + '' => '摍', + '' => '摛', + '' => '摝', + '' => '摴', + '' => '摶', + '' => '摲', + '' => '摳', + '' => '摽', + '' => '摵', + '' => '摦', + '' => '撦', + '' => '摎', + '' => '撂', + '' => '摞', + '' => '摜', + '' => '摋', + '' => '摓', + '' => '摠', + '' => '摐', + '' => '摿', + '' => '搿', + '' => '摬', + '' => '摫', + '' => '摙', + '' => '摥', + '' => '摷', + '' => '敳', + '' => '斠', + '' => '暡', + '' => '暠', + '' => '暟', + '' => '朅', + '' => '朄', + '' => '朢', + '' => '榱', + '' => '榶', + '' => '槉', + '@' => '榠', + 'A' => '槎', + 'B' => '榖', + 'C' => '榰', + 'D' => '榬', + 'E' => '榼', + 'F' => '榑', + 'G' => '榙', + 'H' => '榎', + 'I' => '榧', + 'J' => '榍', + 'K' => '榩', + 'L' => '榾', + 'M' => '榯', + 'N' => '榿', + 'O' => '槄', + 'P' => '榽', + 'Q' => '榤', + 'R' => '槔', + 'S' => '榹', + 'T' => '槊', + 'U' => '榚', + 'V' => '槏', + 'W' => '榳', + 'X' => '榓', + 'Y' => '榪', + 'Z' => '榡', + '[' => '榞', + '\\' => '槙', + ']' => '榗', + '^' => '榐', + '_' => '槂', + '`' => '榵', + 'a' => '榥', + 'b' => '槆', + 'c' => '歊', + 'd' => '歍', + 'e' => '歋', + 'f' => '殞', + 'g' => '殟', + 'h' => '殠', + 'i' => '毃', + 'j' => '毄', + 'k' => '毾', + 'l' => '滎', + 'm' => '滵', + 'n' => '滱', + 'o' => '漃', + 'p' => '漥', + 'q' => '滸', + 'r' => '漷', + 's' => '滻', + 't' => '漮', + 'u' => '漉', + 'v' => '潎', + 'w' => '漙', + 'x' => '漚', + 'y' => '漧', + 'z' => '漘', + '{' => '漻', + '|' => '漒', + '}' => '滭', + '~' => '漊', + '' => '漶', + '' => '潳', + '' => '滹', + '' => '滮', + '' => '漭', + '' => '潀', + '' => '漰', + '' => '漼', + '' => '漵', + '' => '滫', + '' => '漇', + '' => '漎', + '' => '潃', + '' => '漅', + '' => '滽', + '' => '滶', + '' => '漹', + '' => '漜', + '' => '滼', + '' => '漺', + '' => '漟', + '' => '漍', + '' => '漞', + '' => '漈', + '' => '漡', + '' => '熇', + '' => '熐', + '' => '熉', + '' => '熀', + '' => '熅', + '' => '熂', + '' => '熏', + '' => '煻', + '' => '熆', + '' => '熁', + '' => '熗', + '' => '牄', + '' => '牓', + '' => '犗', + '' => '犕', + '' => '犓', + '' => '獃', + '' => '獍', + '' => '獑', + '' => '獌', + '' => '瑢', + '' => '瑳', + '' => '瑱', + '' => '瑵', + '' => '瑲', + '' => '瑧', + '' => '瑮', + '' => '甀', + '' => '甂', + '' => '甃', + '' => '畽', + '' => '疐', + '' => '瘖', + '' => '瘈', + '' => '瘌', + '' => '瘕', + '' => '瘑', + '' => '瘊', + '' => '瘔', + '' => '皸', + '' => '瞁', + '' => '睼', + '' => '瞅', + '' => '瞂', + '' => '睮', + '' => '瞀', + '' => '睯', + '' => '睾', + '' => '瞃', + '' => '碲', + '' => '碪', + '' => '碴', + '' => '碭', + '' => '碨', + '' => '硾', + '' => '碫', + '' => '碞', + '' => '碥', + '' => '碠', + '' => '碬', + '' => '碢', + '' => '碤', + '' => '禘', + '' => '禊', + '' => '禋', + '' => '禖', + '' => '禕', + '' => '禔', + '' => '禓', + '@' => '禗', + 'A' => '禈', + 'B' => '禒', + 'C' => '禐', + 'D' => '稫', + 'E' => '穊', + 'F' => '稰', + 'G' => '稯', + 'H' => '稨', + 'I' => '稦', + 'J' => '窨', + 'K' => '窫', + 'L' => '窬', + 'M' => '竮', + 'N' => '箈', + 'O' => '箜', + 'P' => '箊', + 'Q' => '箑', + 'R' => '箐', + 'S' => '箖', + 'T' => '箍', + 'U' => '箌', + 'V' => '箛', + 'W' => '箎', + 'X' => '箅', + 'Y' => '箘', + 'Z' => '劄', + '[' => '箙', + '\\' => '箤', + ']' => '箂', + '^' => '粻', + '_' => '粿', + '`' => '粼', + 'a' => '粺', + 'b' => '綧', + 'c' => '綷', + 'd' => '緂', + 'e' => '綣', + 'f' => '綪', + 'g' => '緁', + 'h' => '緀', + 'i' => '緅', + 'j' => '綝', + 'k' => '緎', + 'l' => '緄', + 'm' => '緆', + 'n' => '緋', + 'o' => '緌', + 'p' => '綯', + 'q' => '綹', + 'r' => '綖', + 's' => '綼', + 't' => '綟', + 'u' => '綦', + 'v' => '綮', + 'w' => '綩', + 'x' => '綡', + 'y' => '緉', + 'z' => '罳', + '{' => '翢', + '|' => '翣', + '}' => '翥', + '~' => '翞', + '' => '耤', + '' => '聝', + '' => '聜', + '' => '膉', + '' => '膆', + '' => '膃', + '' => '膇', + '' => '膍', + '' => '膌', + '' => '膋', + '' => '舕', + '' => '蒗', + '' => '蒤', + '' => '蒡', + '' => '蒟', + '' => '蒺', + '' => '蓎', + '' => '蓂', + '' => '蒬', + '' => '蒮', + '' => '蒫', + '' => '蒹', + '' => '蒴', + '' => '蓁', + '' => '蓍', + '' => '蒪', + '' => '蒚', + '' => '蒱', + '' => '蓐', + '' => '蒝', + '' => '蒧', + '' => '蒻', + '' => '蒢', + '' => '蒔', + '' => '蓇', + '' => '蓌', + '' => '蒛', + '' => '蒩', + '' => '蒯', + '' => '蒨', + '' => '蓖', + '' => '蒘', + '' => '蒶', + '' => '蓏', + '' => '蒠', + '' => '蓗', + '' => '蓔', + '' => '蓒', + '' => '蓛', + '' => '蒰', + '' => '蒑', + '' => '虡', + '' => '蜳', + '' => '蜣', + '' => '蜨', + '' => '蝫', + '' => '蝀', + '' => '蜮', + '' => '蜞', + '' => '蜡', + '' => '蜙', + '' => '蜛', + '' => '蝃', + '' => '蜬', + '' => '蝁', + '' => '蜾', + '' => '蝆', + '' => '蜠', + '' => '蜲', + '' => '蜪', + '' => '蜭', + '' => '蜼', + '' => '蜒', + '' => '蜺', + '' => '蜱', + '' => '蜵', + '' => '蝂', + '' => '蜦', + '' => '蜧', + '' => '蜸', + '' => '蜤', + '' => '蜚', + '' => '蜰', + '' => '蜑', + '' => '裷', + '' => '裧', + '' => '裱', + '' => '裲', + '' => '裺', + '' => '裾', + '' => '裮', + '' => '裼', + '' => '裶', + '' => '裻', + '@' => '裰', + 'A' => '裬', + 'B' => '裫', + 'C' => '覝', + 'D' => '覡', + 'E' => '覟', + 'F' => '覞', + 'G' => '觩', + 'H' => '觫', + 'I' => '觨', + 'J' => '誫', + 'K' => '誙', + 'L' => '誋', + 'M' => '誒', + 'N' => '誏', + 'O' => '誖', + 'P' => '谽', + 'Q' => '豨', + 'R' => '豩', + 'S' => '賕', + 'T' => '賏', + 'U' => '賗', + 'V' => '趖', + 'W' => '踉', + 'X' => '踂', + 'Y' => '跿', + 'Z' => '踍', + '[' => '跽', + '\\' => '踊', + ']' => '踃', + '^' => '踇', + '_' => '踆', + '`' => '踅', + 'a' => '跾', + 'b' => '踀', + 'c' => '踄', + 'd' => '輐', + 'e' => '輑', + 'f' => '輎', + 'g' => '輍', + 'h' => '鄣', + 'i' => '鄜', + 'j' => '鄠', + 'k' => '鄢', + 'l' => '鄟', + 'm' => '鄝', + 'n' => '鄚', + 'o' => '鄤', + 'p' => '鄡', + 'q' => '鄛', + 'r' => '酺', + 's' => '酲', + 't' => '酹', + 'u' => '酳', + 'v' => '銥', + 'w' => '銤', + 'x' => '鉶', + 'y' => '銛', + 'z' => '鉺', + '{' => '銠', + '|' => '銔', + '}' => '銪', + '~' => '銍', + '' => '銦', + '' => '銚', + '' => '銫', + '' => '鉹', + '' => '銗', + '' => '鉿', + '' => '銣', + '' => '鋮', + '' => '銎', + '' => '銂', + '' => '銕', + '' => '銢', + '' => '鉽', + '' => '銈', + '' => '銡', + '' => '銊', + '' => '銆', + '' => '銌', + '' => '銙', + '' => '銧', + '' => '鉾', + '' => '銇', + '' => '銩', + '' => '銝', + '' => '銋', + '' => '鈭', + '' => '隞', + '' => '隡', + '' => '雿', + '' => '靘', + '' => '靽', + '' => '靺', + '' => '靾', + '' => '鞃', + '' => '鞀', + '' => '鞂', + '' => '靻', + '' => '鞄', + '' => '鞁', + '' => '靿', + '' => '韎', + '' => '韍', + '' => '頖', + '' => '颭', + '' => '颮', + '' => '餂', + '' => '餀', + '' => '餇', + '' => '馝', + '' => '馜', + '' => '駃', + '' => '馹', + '' => '馻', + '' => '馺', + '' => '駂', + '' => '馽', + '' => '駇', + '' => '骱', + '' => '髣', + '' => '髧', + '' => '鬾', + '' => '鬿', + '' => '魠', + '' => '魡', + '' => '魟', + '' => '鳱', + '' => '鳲', + '' => '鳵', + '' => '麧', + '' => '僿', + '' => '儃', + '' => '儰', + '' => '僸', + '' => '儆', + '' => '儇', + '' => '僶', + '' => '僾', + '' => '儋', + '' => '儌', + '' => '僽', + '' => '儊', + '' => '劋', + '' => '劌', + '' => '勱', + '' => '勯', + '' => '噈', + '' => '噂', + '' => '噌', + '' => '嘵', + '' => '噁', + '' => '噊', + '' => '噉', + '' => '噆', + '' => '噘', + '@' => '噚', + 'A' => '噀', + 'B' => '嘳', + 'C' => '嘽', + 'D' => '嘬', + 'E' => '嘾', + 'F' => '嘸', + 'G' => '嘪', + 'H' => '嘺', + 'I' => '圚', + 'J' => '墫', + 'K' => '墝', + 'L' => '墱', + 'M' => '墠', + 'N' => '墣', + 'O' => '墯', + 'P' => '墬', + 'Q' => '墥', + 'R' => '墡', + 'S' => '壿', + 'T' => '嫿', + 'U' => '嫴', + 'V' => '嫽', + 'W' => '嫷', + 'X' => '嫶', + 'Y' => '嬃', + 'Z' => '嫸', + '[' => '嬂', + '\\' => '嫹', + ']' => '嬁', + '^' => '嬇', + '_' => '嬅', + '`' => '嬏', + 'a' => '屧', + 'b' => '嶙', + 'c' => '嶗', + 'd' => '嶟', + 'e' => '嶒', + 'f' => '嶢', + 'g' => '嶓', + 'h' => '嶕', + 'i' => '嶠', + 'j' => '嶜', + 'k' => '嶡', + 'l' => '嶚', + 'm' => '嶞', + 'n' => '幩', + 'o' => '幝', + 'p' => '幠', + 'q' => '幜', + 'r' => '緳', + 's' => '廛', + 't' => '廞', + 'u' => '廡', + 'v' => '彉', + 'w' => '徲', + 'x' => '憋', + 'y' => '憃', + 'z' => '慹', + '{' => '憱', + '|' => '憰', + '}' => '憢', + '~' => '憉', + '' => '憛', + '' => '憓', + '' => '憯', + '' => '憭', + '' => '憟', + '' => '憒', + '' => '憪', + '' => '憡', + '' => '憍', + '' => '慦', + '' => '憳', + '' => '戭', + '' => '摮', + '' => '摰', + '' => '撖', + '' => '撠', + '' => '撅', + '' => '撗', + '' => '撜', + '' => '撏', + '' => '撋', + '' => '撊', + '' => '撌', + '' => '撣', + '' => '撟', + '' => '摨', + '' => '撱', + '' => '撘', + '' => '敶', + '' => '敺', + '' => '敹', + '' => '敻', + '' => '斲', + '' => '斳', + '' => '暵', + '' => '暰', + '' => '暩', + '' => '暲', + '' => '暷', + '' => '暪', + '' => '暯', + '' => '樀', + '' => '樆', + '' => '樗', + '' => '槥', + '' => '槸', + '' => '樕', + '' => '槱', + '' => '槤', + '' => '樠', + '' => '槿', + '' => '槬', + '' => '槢', + '' => '樛', + '' => '樝', + '' => '槾', + '' => '樧', + '' => '槲', + '' => '槮', + '' => '樔', + '' => '槷', + '' => '槧', + '' => '橀', + '' => '樈', + '' => '槦', + '' => '槻', + '' => '樍', + '' => '槼', + '' => '槫', + '' => '樉', + '' => '樄', + '' => '樘', + '' => '樥', + '' => '樏', + '' => '槶', + '' => '樦', + '' => '樇', + '' => '槴', + '' => '樖', + '' => '歑', + '' => '殥', + '' => '殣', + '' => '殢', + '' => '殦', + '' => '氁', + '' => '氀', + '' => '毿', + '' => '氂', + '' => '潁', + '' => '漦', + '' => '潾', + '' => '澇', + '' => '濆', + '' => '澒', + '@' => '澍', + 'A' => '澉', + 'B' => '澌', + 'C' => '潢', + 'D' => '潏', + 'E' => '澅', + 'F' => '潚', + 'G' => '澖', + 'H' => '潶', + 'I' => '潬', + 'J' => '澂', + 'K' => '潕', + 'L' => '潲', + 'M' => '潒', + 'N' => '潐', + 'O' => '潗', + 'P' => '澔', + 'Q' => '澓', + 'R' => '潝', + 'S' => '漀', + 'T' => '潡', + 'U' => '潫', + 'V' => '潽', + 'W' => '潧', + 'X' => '澐', + 'Y' => '潓', + 'Z' => '澋', + '[' => '潩', + '\\' => '潿', + ']' => '澕', + '^' => '潣', + '_' => '潷', + '`' => '潪', + 'a' => '潻', + 'b' => '熲', + 'c' => '熯', + 'd' => '熛', + 'e' => '熰', + 'f' => '熠', + 'g' => '熚', + 'h' => '熩', + 'i' => '熵', + 'j' => '熝', + 'k' => '熥', + 'l' => '熞', + 'm' => '熤', + 'n' => '熡', + 'o' => '熪', + 'p' => '熜', + 'q' => '熧', + 'r' => '熳', + 's' => '犘', + 't' => '犚', + 'u' => '獘', + 'v' => '獒', + 'w' => '獞', + 'x' => '獟', + 'y' => '獠', + 'z' => '獝', + '{' => '獛', + '|' => '獡', + '}' => '獚', + '~' => '獙', + '' => '獢', + '' => '璇', + '' => '璉', + '' => '璊', + '' => '璆', + '' => '璁', + '' => '瑽', + '' => '璅', + '' => '璈', + '' => '瑼', + '' => '瑹', + '' => '甈', + '' => '甇', + '' => '畾', + '' => '瘥', + '' => '瘞', + '' => '瘙', + '' => '瘝', + '' => '瘜', + '' => '瘣', + '' => '瘚', + '' => '瘨', + '' => '瘛', + '' => '皜', + '' => '皝', + '' => '皞', + '' => '皛', + '' => '瞍', + '' => '瞏', + '' => '瞉', + '' => '瞈', + '' => '磍', + '' => '碻', + '' => '磏', + '' => '磌', + '' => '磑', + '' => '磎', + '' => '磔', + '' => '磈', + '' => '磃', + '' => '磄', + '' => '磉', + '' => '禚', + '' => '禡', + '' => '禠', + '' => '禜', + '' => '禢', + '' => '禛', + '' => '歶', + '' => '稹', + '' => '窲', + '' => '窴', + '' => '窳', + '' => '箷', + '' => '篋', + '' => '箾', + '' => '箬', + '' => '篎', + '' => '箯', + '' => '箹', + '' => '篊', + '' => '箵', + '' => '糅', + '' => '糈', + '' => '糌', + '' => '糋', + '' => '緷', + '' => '緛', + '' => '緪', + '' => '緧', + '' => '緗', + '' => '緡', + '' => '縃', + '' => '緺', + '' => '緦', + '' => '緶', + '' => '緱', + '' => '緰', + '' => '緮', + '' => '緟', + '' => '罶', + '' => '羬', + '' => '羰', + '' => '羭', + '' => '翭', + '' => '翫', + '' => '翪', + '' => '翬', + '' => '翦', + '' => '翨', + '' => '聤', + '' => '聧', + '' => '膣', + '' => '膟', + '@' => '膞', + 'A' => '膕', + 'B' => '膢', + 'C' => '膙', + 'D' => '膗', + 'E' => '舖', + 'F' => '艏', + 'G' => '艓', + 'H' => '艒', + 'I' => '艐', + 'J' => '艎', + 'K' => '艑', + 'L' => '蔤', + 'M' => '蔻', + 'N' => '蔏', + 'O' => '蔀', + 'P' => '蔩', + 'Q' => '蔎', + 'R' => '蔉', + 'S' => '蔍', + 'T' => '蔟', + 'U' => '蔊', + 'V' => '蔧', + 'W' => '蔜', + 'X' => '蓻', + 'Y' => '蔫', + 'Z' => '蓺', + '[' => '蔈', + '\\' => '蔌', + ']' => '蓴', + '^' => '蔪', + '_' => '蓲', + '`' => '蔕', + 'a' => '蓷', + 'b' => '蓫', + 'c' => '蓳', + 'd' => '蓼', + 'e' => '蔒', + 'f' => '蓪', + 'g' => '蓩', + 'h' => '蔖', + 'i' => '蓾', + 'j' => '蔨', + 'k' => '蔝', + 'l' => '蔮', + 'm' => '蔂', + 'n' => '蓽', + 'o' => '蔞', + 'p' => '蓶', + 'q' => '蔱', + 'r' => '蔦', + 's' => '蓧', + 't' => '蓨', + 'u' => '蓰', + 'v' => '蓯', + 'w' => '蓹', + 'x' => '蔘', + 'y' => '蔠', + 'z' => '蔰', + '{' => '蔋', + '|' => '蔙', + '}' => '蔯', + '~' => '虢', + '' => '蝖', + '' => '蝣', + '' => '蝤', + '' => '蝷', + '' => '蟡', + '' => '蝳', + '' => '蝘', + '' => '蝔', + '' => '蝛', + '' => '蝒', + '' => '蝡', + '' => '蝚', + '' => '蝑', + '' => '蝞', + '' => '蝭', + '' => '蝪', + '' => '蝐', + '' => '蝎', + '' => '蝟', + '' => '蝝', + '' => '蝯', + '' => '蝬', + '' => '蝺', + '' => '蝮', + '' => '蝜', + '' => '蝥', + '' => '蝏', + '' => '蝻', + '' => '蝵', + '' => '蝢', + '' => '蝧', + '' => '蝩', + '' => '衚', + '' => '褅', + '' => '褌', + '' => '褔', + '' => '褋', + '' => '褗', + '' => '褘', + '' => '褙', + '' => '褆', + '' => '褖', + '' => '褑', + '' => '褎', + '' => '褉', + '' => '覢', + '' => '覤', + '' => '覣', + '' => '觭', + '' => '觰', + '' => '觬', + '' => '諏', + '' => '諆', + '' => '誸', + '' => '諓', + '' => '諑', + '' => '諔', + '' => '諕', + '' => '誻', + '' => '諗', + '' => '誾', + '' => '諀', + '' => '諅', + '' => '諘', + '' => '諃', + '' => '誺', + '' => '誽', + '' => '諙', + '' => '谾', + '' => '豍', + '' => '貏', + '' => '賥', + '' => '賟', + '' => '賙', + '' => '賨', + '' => '賚', + '' => '賝', + '' => '賧', + '' => '趠', + '' => '趜', + '' => '趡', + '' => '趛', + '' => '踠', + '' => '踣', + '' => '踥', + '' => '踤', + '' => '踮', + '' => '踕', + '' => '踛', + '' => '踖', + '' => '踑', + '' => '踙', + '' => '踦', + '' => '踧', + '@' => '踔', + 'A' => '踒', + 'B' => '踘', + 'C' => '踓', + 'D' => '踜', + 'E' => '踗', + 'F' => '踚', + 'G' => '輬', + 'H' => '輤', + 'I' => '輘', + 'J' => '輚', + 'K' => '輠', + 'L' => '輣', + 'M' => '輖', + 'N' => '輗', + 'O' => '遳', + 'P' => '遰', + 'Q' => '遯', + 'R' => '遧', + 'S' => '遫', + 'T' => '鄯', + 'U' => '鄫', + 'V' => '鄩', + 'W' => '鄪', + 'X' => '鄲', + 'Y' => '鄦', + 'Z' => '鄮', + '[' => '醅', + '\\' => '醆', + ']' => '醊', + '^' => '醁', + '_' => '醂', + '`' => '醄', + 'a' => '醀', + 'b' => '鋐', + 'c' => '鋃', + 'd' => '鋄', + 'e' => '鋀', + 'f' => '鋙', + 'g' => '銶', + 'h' => '鋏', + 'i' => '鋱', + 'j' => '鋟', + 'k' => '鋘', + 'l' => '鋩', + 'm' => '鋗', + 'n' => '鋝', + 'o' => '鋌', + 'p' => '鋯', + 'q' => '鋂', + 'r' => '鋨', + 's' => '鋊', + 't' => '鋈', + 'u' => '鋎', + 'v' => '鋦', + 'w' => '鋍', + 'x' => '鋕', + 'y' => '鋉', + 'z' => '鋠', + '{' => '鋞', + '|' => '鋧', + '}' => '鋑', + '~' => '鋓', + '' => '銵', + '' => '鋡', + '' => '鋆', + '' => '銴', + '' => '镼', + '' => '閬', + '' => '閫', + '' => '閮', + '' => '閰', + '' => '隤', + '' => '隢', + '' => '雓', + '' => '霅', + '' => '霈', + '' => '霂', + '' => '靚', + '' => '鞊', + '' => '鞎', + '' => '鞈', + '' => '韐', + '' => '韏', + '' => '頞', + '' => '頝', + '' => '頦', + '' => '頩', + '' => '頨', + '' => '頠', + '' => '頛', + '' => '頧', + '' => '颲', + '' => '餈', + '' => '飺', + '' => '餑', + '' => '餔', + '' => '餖', + '' => '餗', + '' => '餕', + '' => '駜', + '' => '駍', + '' => '駏', + '' => '駓', + '' => '駔', + '' => '駎', + '' => '駉', + '' => '駖', + '' => '駘', + '' => '駋', + '' => '駗', + '' => '駌', + '' => '骳', + '' => '髬', + '' => '髫', + '' => '髳', + '' => '髲', + '' => '髱', + '' => '魆', + '' => '魃', + '' => '魧', + '' => '魴', + '' => '魱', + '' => '魦', + '' => '魶', + '' => '魵', + '' => '魰', + '' => '魨', + '' => '魤', + '' => '魬', + '' => '鳼', + '' => '鳺', + '' => '鳽', + '' => '鳿', + '' => '鳷', + '' => '鴇', + '' => '鴀', + '' => '鳹', + '' => '鳻', + '' => '鴈', + '' => '鴅', + '' => '鴄', + '' => '麃', + '' => '黓', + '' => '鼏', + '' => '鼐', + '' => '儜', + '' => '儓', + '' => '儗', + '' => '儚', + '' => '儑', + '' => '凞', + '' => '匴', + '' => '叡', + '' => '噰', + '' => '噠', + '' => '噮', + '@' => '噳', + 'A' => '噦', + 'B' => '噣', + 'C' => '噭', + 'D' => '噲', + 'E' => '噞', + 'F' => '噷', + 'G' => '圜', + 'H' => '圛', + 'I' => '壈', + 'J' => '墽', + 'K' => '壉', + 'L' => '墿', + 'M' => '墺', + 'N' => '壂', + 'O' => '墼', + 'P' => '壆', + 'Q' => '嬗', + 'R' => '嬙', + 'S' => '嬛', + 'T' => '嬡', + 'U' => '嬔', + 'V' => '嬓', + 'W' => '嬐', + 'X' => '嬖', + 'Y' => '嬨', + 'Z' => '嬚', + '[' => '嬠', + '\\' => '嬞', + ']' => '寯', + '^' => '嶬', + '_' => '嶱', + '`' => '嶩', + 'a' => '嶧', + 'b' => '嶵', + 'c' => '嶰', + 'd' => '嶮', + 'e' => '嶪', + 'f' => '嶨', + 'g' => '嶲', + 'h' => '嶭', + 'i' => '嶯', + 'j' => '嶴', + 'k' => '幧', + 'l' => '幨', + 'm' => '幦', + 'n' => '幯', + 'o' => '廩', + 'p' => '廧', + 'q' => '廦', + 'r' => '廨', + 's' => '廥', + 't' => '彋', + 'u' => '徼', + 'v' => '憝', + 'w' => '憨', + 'x' => '憖', + 'y' => '懅', + 'z' => '憴', + '{' => '懆', + '|' => '懁', + '}' => '懌', + '~' => '憺', + '' => '憿', + '' => '憸', + '' => '憌', + '' => '擗', + '' => '擖', + '' => '擐', + '' => '擏', + '' => '擉', + '' => '撽', + '' => '撉', + '' => '擃', + '' => '擛', + '' => '擳', + '' => '擙', + '' => '攳', + '' => '敿', + '' => '敼', + '' => '斢', + '' => '曈', + '' => '暾', + '' => '曀', + '' => '曊', + '' => '曋', + '' => '曏', + '' => '暽', + '' => '暻', + '' => '暺', + '' => '曌', + '' => '朣', + '' => '樴', + '' => '橦', + '' => '橉', + '' => '橧', + '' => '樲', + '' => '橨', + '' => '樾', + '' => '橝', + '' => '橭', + '' => '橶', + '' => '橛', + '' => '橑', + '' => '樨', + '' => '橚', + '' => '樻', + '' => '樿', + '' => '橁', + '' => '橪', + '' => '橤', + '' => '橐', + '' => '橏', + '' => '橔', + '' => '橯', + '' => '橩', + '' => '橠', + '' => '樼', + '' => '橞', + '' => '橖', + '' => '橕', + '' => '橍', + '' => '橎', + '' => '橆', + '' => '歕', + '' => '歔', + '' => '歖', + '' => '殧', + '' => '殪', + '' => '殫', + '' => '毈', + '' => '毇', + '' => '氄', + '' => '氃', + '' => '氆', + '' => '澭', + '' => '濋', + '' => '澣', + '' => '濇', + '' => '澼', + '' => '濎', + '' => '濈', + '' => '潞', + '' => '濄', + '' => '澽', + '' => '澞', + '' => '濊', + '' => '澨', + '' => '瀄', + '' => '澥', + '' => '澮', + '' => '澺', + '' => '澬', + '' => '澪', + '' => '濏', + '' => '澿', + '' => '澸', + '@' => '澢', + 'A' => '濉', + 'B' => '澫', + 'C' => '濍', + 'D' => '澯', + 'E' => '澲', + 'F' => '澰', + 'G' => '燅', + 'H' => '燂', + 'I' => '熿', + 'J' => '熸', + 'K' => '燖', + 'L' => '燀', + 'M' => '燁', + 'N' => '燋', + 'O' => '燔', + 'P' => '燊', + 'Q' => '燇', + 'R' => '燏', + 'S' => '熽', + 'T' => '燘', + 'U' => '熼', + 'V' => '燆', + 'W' => '燚', + 'X' => '燛', + 'Y' => '犝', + 'Z' => '犞', + '[' => '獩', + '\\' => '獦', + ']' => '獧', + '^' => '獬', + '_' => '獥', + '`' => '獫', + 'a' => '獪', + 'b' => '瑿', + 'c' => '璚', + 'd' => '璠', + 'e' => '璔', + 'f' => '璒', + 'g' => '璕', + 'h' => '璡', + 'i' => '甋', + 'j' => '疀', + 'k' => '瘯', + 'l' => '瘭', + 'm' => '瘱', + 'n' => '瘽', + 'o' => '瘳', + 'p' => '瘼', + 'q' => '瘵', + 'r' => '瘲', + 's' => '瘰', + 't' => '皻', + 'u' => '盦', + 'v' => '瞚', + 'w' => '瞝', + 'x' => '瞡', + 'y' => '瞜', + 'z' => '瞛', + '{' => '瞢', + '|' => '瞣', + '}' => '瞕', + '~' => '瞙', + '' => '瞗', + '' => '磝', + '' => '磩', + '' => '磥', + '' => '磪', + '' => '磞', + '' => '磣', + '' => '磛', + '' => '磡', + '' => '磢', + '' => '磭', + '' => '磟', + '' => '磠', + '' => '禤', + '' => '穄', + '' => '穈', + '' => '穇', + '' => '窶', + '' => '窸', + '' => '窵', + '' => '窱', + '' => '窷', + '' => '篞', + '' => '篣', + '' => '篧', + '' => '篝', + '' => '篕', + '' => '篥', + '' => '篚', + '' => '篨', + '' => '篹', + '' => '篔', + '' => '篪', + '' => '篢', + '' => '篜', + '' => '篫', + '' => '篘', + '' => '篟', + '' => '糒', + '' => '糔', + '' => '糗', + '' => '糐', + '' => '糑', + '' => '縒', + '' => '縡', + '' => '縗', + '' => '縌', + '' => '縟', + '' => '縠', + '' => '縓', + '' => '縎', + '' => '縜', + '' => '縕', + '' => '縚', + '' => '縢', + '' => '縋', + '' => '縏', + '' => '縖', + '' => '縍', + '' => '縔', + '' => '縥', + '' => '縤', + '' => '罃', + '' => '罻', + '' => '罼', + '' => '罺', + '' => '羱', + '' => '翯', + '' => '耪', + '' => '耩', + '' => '聬', + '' => '膱', + '' => '膦', + '' => '膮', + '' => '膹', + '' => '膵', + '' => '膫', + '' => '膰', + '' => '膬', + '' => '膴', + '' => '膲', + '' => '膷', + '' => '膧', + '' => '臲', + '' => '艕', + '' => '艖', + '' => '艗', + '' => '蕖', + '' => '蕅', + '' => '蕫', + '' => '蕍', + '' => '蕓', + '' => '蕡', + '' => '蕘', + '@' => '蕀', + 'A' => '蕆', + 'B' => '蕤', + 'C' => '蕁', + 'D' => '蕢', + 'E' => '蕄', + 'F' => '蕑', + 'G' => '蕇', + 'H' => '蕣', + 'I' => '蔾', + 'J' => '蕛', + 'K' => '蕱', + 'L' => '蕎', + 'M' => '蕮', + 'N' => '蕵', + 'O' => '蕕', + 'P' => '蕧', + 'Q' => '蕠', + 'R' => '薌', + 'S' => '蕦', + 'T' => '蕝', + 'U' => '蕔', + 'V' => '蕥', + 'W' => '蕬', + 'X' => '虣', + 'Y' => '虥', + 'Z' => '虤', + '[' => '螛', + '\\' => '螏', + ']' => '螗', + '^' => '螓', + '_' => '螒', + '`' => '螈', + 'a' => '螁', + 'b' => '螖', + 'c' => '螘', + 'd' => '蝹', + 'e' => '螇', + 'f' => '螣', + 'g' => '螅', + 'h' => '螐', + 'i' => '螑', + 'j' => '螝', + 'k' => '螄', + 'l' => '螔', + 'm' => '螜', + 'n' => '螚', + 'o' => '螉', + 'p' => '褞', + 'q' => '褦', + 'r' => '褰', + 's' => '褭', + 't' => '褮', + 'u' => '褧', + 'v' => '褱', + 'w' => '褢', + 'x' => '褩', + 'y' => '褣', + 'z' => '褯', + '{' => '褬', + '|' => '褟', + '}' => '觱', + '~' => '諠', + '' => '諢', + '' => '諲', + '' => '諴', + '' => '諵', + '' => '諝', + '' => '謔', + '' => '諤', + '' => '諟', + '' => '諰', + '' => '諈', + '' => '諞', + '' => '諡', + '' => '諨', + '' => '諿', + '' => '諯', + '' => '諻', + '' => '貑', + '' => '貒', + '' => '貐', + '' => '賵', + '' => '賮', + '' => '賱', + '' => '賰', + '' => '賳', + '' => '赬', + '' => '赮', + '' => '趥', + '' => '趧', + '' => '踳', + '' => '踾', + '' => '踸', + '' => '蹀', + '' => '蹅', + '' => '踶', + '' => '踼', + '' => '踽', + '' => '蹁', + '' => '踰', + '' => '踿', + '' => '躽', + '' => '輶', + '' => '輮', + '' => '輵', + '' => '輲', + '' => '輹', + '' => '輷', + '' => '輴', + '' => '遶', + '' => '遹', + '' => '遻', + '' => '邆', + '' => '郺', + '' => '鄳', + '' => '鄵', + '' => '鄶', + '' => '醓', + '' => '醐', + '' => '醑', + '' => '醍', + '' => '醏', + '' => '錧', + '' => '錞', + '' => '錈', + '' => '錟', + '' => '錆', + '' => '錏', + '' => '鍺', + '' => '錸', + '' => '錼', + '' => '錛', + '' => '錣', + '' => '錒', + '' => '錁', + '' => '鍆', + '' => '錭', + '' => '錎', + '' => '錍', + '' => '鋋', + '' => '錝', + '' => '鋺', + '' => '錥', + '' => '錓', + '' => '鋹', + '' => '鋷', + '' => '錴', + '' => '錂', + '' => '錤', + '' => '鋿', + '' => '錩', + '' => '錹', + '' => '錵', + '' => '錪', + '' => '錔', + '' => '錌', + '@' => '錋', + 'A' => '鋾', + 'B' => '錉', + 'C' => '錀', + 'D' => '鋻', + 'E' => '錖', + 'F' => '閼', + 'G' => '闍', + 'H' => '閾', + 'I' => '閹', + 'J' => '閺', + 'K' => '閶', + 'L' => '閿', + 'M' => '閵', + 'N' => '閽', + 'O' => '隩', + 'P' => '雔', + 'Q' => '霋', + 'R' => '霒', + 'S' => '霐', + 'T' => '鞙', + 'U' => '鞗', + 'V' => '鞔', + 'W' => '韰', + 'X' => '韸', + 'Y' => '頵', + 'Z' => '頯', + '[' => '頲', + '\\' => '餤', + ']' => '餟', + '^' => '餧', + '_' => '餩', + '`' => '馞', + 'a' => '駮', + 'b' => '駬', + 'c' => '駥', + 'd' => '駤', + 'e' => '駰', + 'f' => '駣', + 'g' => '駪', + 'h' => '駩', + 'i' => '駧', + 'j' => '骹', + 'k' => '骿', + 'l' => '骴', + 'm' => '骻', + 'n' => '髶', + 'o' => '髺', + 'p' => '髹', + 'q' => '髷', + 'r' => '鬳', + 's' => '鮀', + 't' => '鮅', + 'u' => '鮇', + 'v' => '魼', + 'w' => '魾', + 'x' => '魻', + 'y' => '鮂', + 'z' => '鮓', + '{' => '鮒', + '|' => '鮐', + '}' => '魺', + '~' => '鮕', + '' => '魽', + '' => '鮈', + '' => '鴥', + '' => '鴗', + '' => '鴠', + '' => '鴞', + '' => '鴔', + '' => '鴩', + '' => '鴝', + '' => '鴘', + '' => '鴢', + '' => '鴐', + '' => '鴙', + '' => '鴟', + '' => '麈', + '' => '麆', + '' => '麇', + '' => '麮', + '' => '麭', + '' => '黕', + '' => '黖', + '' => '黺', + '' => '鼒', + '' => '鼽', + '' => '儦', + '' => '儥', + '' => '儢', + '' => '儤', + '' => '儠', + '' => '儩', + '' => '勴', + '' => '嚓', + '' => '嚌', + '' => '嚍', + '' => '嚆', + '' => '嚄', + '' => '嚃', + '' => '噾', + '' => '嚂', + '' => '噿', + '' => '嚁', + '' => '壖', + '' => '壔', + '' => '壏', + '' => '壒', + '' => '嬭', + '' => '嬥', + '' => '嬲', + '' => '嬣', + '' => '嬬', + '' => '嬧', + '' => '嬦', + '' => '嬯', + '' => '嬮', + '' => '孻', + '' => '寱', + '' => '寲', + '' => '嶷', + '' => '幬', + '' => '幪', + '' => '徾', + '' => '徻', + '' => '懃', + '' => '憵', + '' => '憼', + '' => '懧', + '' => '懠', + '' => '懥', + '' => '懤', + '' => '懨', + '' => '懞', + '' => '擯', + '' => '擩', + '' => '擣', + '' => '擫', + '' => '擤', + '' => '擨', + '' => '斁', + '' => '斀', + '' => '斶', + '' => '旚', + '' => '曒', + '' => '檍', + '' => '檖', + '' => '檁', + '' => '檥', + '' => '檉', + '' => '檟', + '' => '檛', + '' => '檡', + '' => '檞', + '' => '檇', + '' => '檓', + '' => '檎', + '@' => '檕', + 'A' => '檃', + 'B' => '檨', + 'C' => '檤', + 'D' => '檑', + 'E' => '橿', + 'F' => '檦', + 'G' => '檚', + 'H' => '檅', + 'I' => '檌', + 'J' => '檒', + 'K' => '歛', + 'L' => '殭', + 'M' => '氉', + 'N' => '濌', + 'O' => '澩', + 'P' => '濴', + 'Q' => '濔', + 'R' => '濣', + 'S' => '濜', + 'T' => '濭', + 'U' => '濧', + 'V' => '濦', + 'W' => '濞', + 'X' => '濲', + 'Y' => '濝', + 'Z' => '濢', + '[' => '濨', + '\\' => '燡', + ']' => '燱', + '^' => '燨', + '_' => '燲', + '`' => '燤', + 'a' => '燰', + 'b' => '燢', + 'c' => '獳', + 'd' => '獮', + 'e' => '獯', + 'f' => '璗', + 'g' => '璲', + 'h' => '璫', + 'i' => '璐', + 'j' => '璪', + 'k' => '璭', + 'l' => '璱', + 'm' => '璥', + 'n' => '璯', + 'o' => '甐', + 'p' => '甑', + 'q' => '甒', + 'r' => '甏', + 's' => '疄', + 't' => '癃', + 'u' => '癈', + 'v' => '癉', + 'w' => '癇', + 'x' => '皤', + 'y' => '盩', + 'z' => '瞵', + '{' => '瞫', + '|' => '瞲', + '}' => '瞷', + '~' => '瞶', + '' => '瞴', + '' => '瞱', + '' => '瞨', + '' => '矰', + '' => '磳', + '' => '磽', + '' => '礂', + '' => '磻', + '' => '磼', + '' => '磲', + '' => '礅', + '' => '磹', + '' => '磾', + '' => '礄', + '' => '禫', + '' => '禨', + '' => '穜', + '' => '穛', + '' => '穖', + '' => '穘', + '' => '穔', + '' => '穚', + '' => '窾', + '' => '竀', + '' => '竁', + '' => '簅', + '' => '簏', + '' => '篲', + '' => '簀', + '' => '篿', + '' => '篻', + '' => '簎', + '' => '篴', + '' => '簋', + '' => '篳', + '' => '簂', + '' => '簉', + '' => '簃', + '' => '簁', + '' => '篸', + '' => '篽', + '' => '簆', + '' => '篰', + '' => '篱', + '' => '簐', + '' => '簊', + '' => '糨', + '' => '縭', + '' => '縼', + '' => '繂', + '' => '縳', + '' => '顈', + '' => '縸', + '' => '縪', + '' => '繉', + '' => '繀', + '' => '繇', + '' => '縩', + '' => '繌', + '' => '縰', + '' => '縻', + '' => '縶', + '' => '繄', + '' => '縺', + '' => '罅', + '' => '罿', + '' => '罾', + '' => '罽', + '' => '翴', + '' => '翲', + '' => '耬', + '' => '膻', + '' => '臄', + '' => '臌', + '' => '臊', + '' => '臅', + '' => '臇', + '' => '膼', + '' => '臩', + '' => '艛', + '' => '艚', + '' => '艜', + '' => '薃', + '' => '薀', + '' => '薏', + '' => '薧', + '' => '薕', + '' => '薠', + '' => '薋', + '' => '薣', + '' => '蕻', + '' => '薤', + '' => '薚', + '' => '薞', + '@' => '蕷', + 'A' => '蕼', + 'B' => '薉', + 'C' => '薡', + 'D' => '蕺', + 'E' => '蕸', + 'F' => '蕗', + 'G' => '薎', + 'H' => '薖', + 'I' => '薆', + 'J' => '薍', + 'K' => '薙', + 'L' => '薝', + 'M' => '薁', + 'N' => '薢', + 'O' => '薂', + 'P' => '薈', + 'Q' => '薅', + 'R' => '蕹', + 'S' => '蕶', + 'T' => '薘', + 'U' => '薐', + 'V' => '薟', + 'W' => '虨', + 'X' => '螾', + 'Y' => '螪', + 'Z' => '螭', + '[' => '蟅', + '\\' => '螰', + ']' => '螬', + '^' => '螹', + '_' => '螵', + '`' => '螼', + 'a' => '螮', + 'b' => '蟉', + 'c' => '蟃', + 'd' => '蟂', + 'e' => '蟌', + 'f' => '螷', + 'g' => '螯', + 'h' => '蟄', + 'i' => '蟊', + 'j' => '螴', + 'k' => '螶', + 'l' => '螿', + 'm' => '螸', + 'n' => '螽', + 'o' => '蟞', + 'p' => '螲', + 'q' => '褵', + 'r' => '褳', + 's' => '褼', + 't' => '褾', + 'u' => '襁', + 'v' => '襒', + 'w' => '褷', + 'x' => '襂', + 'y' => '覭', + 'z' => '覯', + '{' => '覮', + '|' => '觲', + '}' => '觳', + '~' => '謞', + '' => '謘', + '' => '謖', + '' => '謑', + '' => '謅', + '' => '謋', + '' => '謢', + '' => '謏', + '' => '謒', + '' => '謕', + '' => '謇', + '' => '謍', + '' => '謈', + '' => '謆', + '' => '謜', + '' => '謓', + '' => '謚', + '' => '豏', + '' => '豰', + '' => '豲', + '' => '豱', + '' => '豯', + '' => '貕', + '' => '貔', + '' => '賹', + '' => '赯', + '' => '蹎', + '' => '蹍', + '' => '蹓', + '' => '蹐', + '' => '蹌', + '' => '蹇', + '' => '轃', + '' => '轀', + '' => '邅', + '' => '遾', + '' => '鄸', + '' => '醚', + '' => '醢', + '' => '醛', + '' => '醙', + '' => '醟', + '' => '醡', + '' => '醝', + '' => '醠', + '' => '鎡', + '' => '鎃', + '' => '鎯', + '' => '鍤', + '' => '鍖', + '' => '鍇', + '' => '鍼', + '' => '鍘', + '' => '鍜', + '' => '鍶', + '' => '鍉', + '' => '鍐', + '' => '鍑', + '' => '鍠', + '' => '鍭', + '' => '鎏', + '' => '鍌', + '' => '鍪', + '' => '鍹', + '' => '鍗', + '' => '鍕', + '' => '鍒', + '' => '鍏', + '' => '鍱', + '' => '鍷', + '' => '鍻', + '' => '鍡', + '' => '鍞', + '' => '鍣', + '' => '鍧', + '' => '鎀', + '' => '鍎', + '' => '鍙', + '' => '闇', + '' => '闀', + '' => '闉', + '' => '闃', + '' => '闅', + '' => '閷', + '' => '隮', + '' => '隰', + '' => '隬', + '' => '霠', + '' => '霟', + '' => '霘', + '' => '霝', + '' => '霙', + '' => '鞚', + '' => '鞡', + '' => '鞜', + '@' => '鞞', + 'A' => '鞝', + 'B' => '韕', + 'C' => '韔', + 'D' => '韱', + 'E' => '顁', + 'F' => '顄', + 'G' => '顊', + 'H' => '顉', + 'I' => '顅', + 'J' => '顃', + 'K' => '餥', + 'L' => '餫', + 'M' => '餬', + 'N' => '餪', + 'O' => '餳', + 'P' => '餲', + 'Q' => '餯', + 'R' => '餭', + 'S' => '餱', + 'T' => '餰', + 'U' => '馘', + 'V' => '馣', + 'W' => '馡', + 'X' => '騂', + 'Y' => '駺', + 'Z' => '駴', + '[' => '駷', + '\\' => '駹', + ']' => '駸', + '^' => '駶', + '_' => '駻', + '`' => '駽', + 'a' => '駾', + 'b' => '駼', + 'c' => '騃', + 'd' => '骾', + 'e' => '髾', + 'f' => '髽', + 'g' => '鬁', + 'h' => '髼', + 'i' => '魈', + 'j' => '鮚', + 'k' => '鮨', + 'l' => '鮞', + 'm' => '鮛', + 'n' => '鮦', + 'o' => '鮡', + 'p' => '鮥', + 'q' => '鮤', + 'r' => '鮆', + 's' => '鮢', + 't' => '鮠', + 'u' => '鮯', + 'v' => '鴳', + 'w' => '鵁', + 'x' => '鵧', + 'y' => '鴶', + 'z' => '鴮', + '{' => '鴯', + '|' => '鴱', + '}' => '鴸', + '~' => '鴰', + '' => '鵅', + '' => '鵂', + '' => '鵃', + '' => '鴾', + '' => '鴷', + '' => '鵀', + '' => '鴽', + '' => '翵', + '' => '鴭', + '' => '麊', + '' => '麉', + '' => '麍', + '' => '麰', + '' => '黈', + '' => '黚', + '' => '黻', + '' => '黿', + '' => '鼤', + '' => '鼣', + '' => '鼢', + '' => '齔', + '' => '龠', + '' => '儱', + '' => '儭', + '' => '儮', + '' => '嚘', + '' => '嚜', + '' => '嚗', + '' => '嚚', + '' => '嚝', + '' => '嚙', + '' => '奰', + '' => '嬼', + '' => '屩', + '' => '屪', + '' => '巀', + '' => '幭', + '' => '幮', + '' => '懘', + '' => '懟', + '' => '懭', + '' => '懮', + '' => '懱', + '' => '懪', + '' => '懰', + '' => '懫', + '' => '懖', + '' => '懩', + '' => '擿', + '' => '攄', + '' => '擽', + '' => '擸', + '' => '攁', + '' => '攃', + '' => '擼', + '' => '斔', + '' => '旛', + '' => '曚', + '' => '曛', + '' => '曘', + '' => '櫅', + '' => '檹', + '' => '檽', + '' => '櫡', + '' => '櫆', + '' => '檺', + '' => '檶', + '' => '檷', + '' => '櫇', + '' => '檴', + '' => '檭', + '' => '歞', + '' => '毉', + '' => '氋', + '' => '瀇', + '' => '瀌', + '' => '瀍', + '' => '瀁', + '' => '瀅', + '' => '瀔', + '' => '瀎', + '' => '濿', + '' => '瀀', + '' => '濻', + '' => '瀦', + '' => '濼', + '' => '濷', + '' => '瀊', + '' => '爁', + '' => '燿', + '' => '燹', + '' => '爃', + '' => '燽', + '' => '獶', + '@' => '璸', + 'A' => '瓀', + 'B' => '璵', + 'C' => '瓁', + 'D' => '璾', + 'E' => '璶', + 'F' => '璻', + 'G' => '瓂', + 'H' => '甔', + 'I' => '甓', + 'J' => '癜', + 'K' => '癤', + 'L' => '癙', + 'M' => '癐', + 'N' => '癓', + 'O' => '癗', + 'P' => '癚', + 'Q' => '皦', + 'R' => '皽', + 'S' => '盬', + 'T' => '矂', + 'U' => '瞺', + 'V' => '磿', + 'W' => '礌', + 'X' => '礓', + 'Y' => '礔', + 'Z' => '礉', + '[' => '礐', + '\\' => '礒', + ']' => '礑', + '^' => '禭', + '_' => '禬', + '`' => '穟', + 'a' => '簜', + 'b' => '簩', + 'c' => '簙', + 'd' => '簠', + 'e' => '簟', + 'f' => '簭', + 'g' => '簝', + 'h' => '簦', + 'i' => '簨', + 'j' => '簢', + 'k' => '簥', + 'l' => '簰', + 'm' => '繜', + 'n' => '繐', + 'o' => '繖', + 'p' => '繣', + 'q' => '繘', + 'r' => '繢', + 's' => '繟', + 't' => '繑', + 'u' => '繠', + 'v' => '繗', + 'w' => '繓', + 'x' => '羵', + 'y' => '羳', + 'z' => '翷', + '{' => '翸', + '|' => '聵', + '}' => '臑', + '~' => '臒', + '' => '臐', + '' => '艟', + '' => '艞', + '' => '薴', + '' => '藆', + '' => '藀', + '' => '藃', + '' => '藂', + '' => '薳', + '' => '薵', + '' => '薽', + '' => '藇', + '' => '藄', + '' => '薿', + '' => '藋', + '' => '藎', + '' => '藈', + '' => '藅', + '' => '薱', + '' => '薶', + '' => '藒', + '' => '蘤', + '' => '薸', + '' => '薷', + '' => '薾', + '' => '虩', + '' => '蟧', + '' => '蟦', + '' => '蟢', + '' => '蟛', + '' => '蟫', + '' => '蟪', + '' => '蟥', + '' => '蟟', + '' => '蟳', + '' => '蟤', + '' => '蟔', + '' => '蟜', + '' => '蟓', + '' => '蟭', + '' => '蟘', + '' => '蟣', + '' => '螤', + '' => '蟗', + '' => '蟙', + '' => '蠁', + '' => '蟴', + '' => '蟨', + '' => '蟝', + '' => '襓', + '' => '襋', + '' => '襏', + '' => '襌', + '' => '襆', + '' => '襐', + '' => '襑', + '' => '襉', + '' => '謪', + '' => '謧', + '' => '謣', + '' => '謳', + '' => '謰', + '' => '謵', + '' => '譇', + '' => '謯', + '' => '謼', + '' => '謾', + '' => '謱', + '' => '謥', + '' => '謷', + '' => '謦', + '' => '謶', + '' => '謮', + '' => '謤', + '' => '謻', + '' => '謽', + '' => '謺', + '' => '豂', + '' => '豵', + '' => '貙', + '' => '貘', + '' => '貗', + '' => '賾', + '' => '贄', + '' => '贂', + '' => '贀', + '' => '蹜', + '' => '蹢', + '' => '蹠', + '' => '蹗', + '' => '蹖', + '' => '蹞', + '' => '蹥', + '' => '蹧', + '@' => '蹛', + 'A' => '蹚', + 'B' => '蹡', + 'C' => '蹝', + 'D' => '蹩', + 'E' => '蹔', + 'F' => '轆', + 'G' => '轇', + 'H' => '轈', + 'I' => '轋', + 'J' => '鄨', + 'K' => '鄺', + 'L' => '鄻', + 'M' => '鄾', + 'N' => '醨', + 'O' => '醥', + 'P' => '醧', + 'Q' => '醯', + 'R' => '醪', + 'S' => '鎵', + 'T' => '鎌', + 'U' => '鎒', + 'V' => '鎷', + 'W' => '鎛', + 'X' => '鎝', + 'Y' => '鎉', + 'Z' => '鎧', + '[' => '鎎', + '\\' => '鎪', + ']' => '鎞', + '^' => '鎦', + '_' => '鎕', + '`' => '鎈', + 'a' => '鎙', + 'b' => '鎟', + 'c' => '鎍', + 'd' => '鎱', + 'e' => '鎑', + 'f' => '鎲', + 'g' => '鎤', + 'h' => '鎨', + 'i' => '鎴', + 'j' => '鎣', + 'k' => '鎥', + 'l' => '闒', + 'm' => '闓', + 'n' => '闑', + 'o' => '隳', + 'p' => '雗', + 'q' => '雚', + 'r' => '巂', + 's' => '雟', + 't' => '雘', + 'u' => '雝', + 'v' => '霣', + 'w' => '霢', + 'x' => '霥', + 'y' => '鞬', + 'z' => '鞮', + '{' => '鞨', + '|' => '鞫', + '}' => '鞤', + '~' => '鞪', + '' => '鞢', + '' => '鞥', + '' => '韗', + '' => '韙', + '' => '韖', + '' => '韘', + '' => '韺', + '' => '顐', + '' => '顑', + '' => '顒', + '' => '颸', + '' => '饁', + '' => '餼', + '' => '餺', + '' => '騏', + '' => '騋', + '' => '騉', + '' => '騍', + '' => '騄', + '' => '騑', + '' => '騊', + '' => '騅', + '' => '騇', + '' => '騆', + '' => '髀', + '' => '髜', + '' => '鬈', + '' => '鬄', + '' => '鬅', + '' => '鬩', + '' => '鬵', + '' => '魊', + '' => '魌', + '' => '魋', + '' => '鯇', + '' => '鯆', + '' => '鯃', + '' => '鮿', + '' => '鯁', + '' => '鮵', + '' => '鮸', + '' => '鯓', + '' => '鮶', + '' => '鯄', + '' => '鮹', + '' => '鮽', + '' => '鵜', + '' => '鵓', + '' => '鵏', + '' => '鵊', + '' => '鵛', + '' => '鵋', + '' => '鵙', + '' => '鵖', + '' => '鵌', + '' => '鵗', + '' => '鵒', + '' => '鵔', + '' => '鵟', + '' => '鵘', + '' => '鵚', + '' => '麎', + '' => '麌', + '' => '黟', + '' => '鼁', + '' => '鼀', + '' => '鼖', + '' => '鼥', + '' => '鼫', + '' => '鼪', + '' => '鼩', + '' => '鼨', + '' => '齌', + '' => '齕', + '' => '儴', + '' => '儵', + '' => '劖', + '' => '勷', + '' => '厴', + '' => '嚫', + '' => '嚭', + '' => '嚦', + '' => '嚧', + '' => '嚪', + '' => '嚬', + '' => '壚', + '' => '壝', + '' => '壛', + '' => '夒', + '' => '嬽', + '' => '嬾', + '' => '嬿', + '' => '巃', + '' => '幰', + '@' => '徿', + 'A' => '懻', + 'B' => '攇', + 'C' => '攐', + 'D' => '攍', + 'E' => '攉', + 'F' => '攌', + 'G' => '攎', + 'H' => '斄', + 'I' => '旞', + 'J' => '旝', + 'K' => '曞', + 'L' => '櫧', + 'M' => '櫠', + 'N' => '櫌', + 'O' => '櫑', + 'P' => '櫙', + 'Q' => '櫋', + 'R' => '櫟', + 'S' => '櫜', + 'T' => '櫐', + 'U' => '櫫', + 'V' => '櫏', + 'W' => '櫍', + 'X' => '櫞', + 'Y' => '歠', + 'Z' => '殰', + '[' => '氌', + '\\' => '瀙', + ']' => '瀧', + '^' => '瀠', + '_' => '瀖', + '`' => '瀫', + 'a' => '瀡', + 'b' => '瀢', + 'c' => '瀣', + 'd' => '瀩', + 'e' => '瀗', + 'f' => '瀤', + 'g' => '瀜', + 'h' => '瀪', + 'i' => '爌', + 'j' => '爊', + 'k' => '爇', + 'l' => '爂', + 'm' => '爅', + 'n' => '犥', + 'o' => '犦', + 'p' => '犤', + 'q' => '犣', + 'r' => '犡', + 's' => '瓋', + 't' => '瓅', + 'u' => '璷', + 'v' => '瓃', + 'w' => '甖', + 'x' => '癠', + 'y' => '矉', + 'z' => '矊', + '{' => '矄', + '|' => '矱', + '}' => '礝', + '~' => '礛', + '' => '礡', + '' => '礜', + '' => '礗', + '' => '礞', + '' => '禰', + '' => '穧', + '' => '穨', + '' => '簳', + '' => '簼', + '' => '簹', + '' => '簬', + '' => '簻', + '' => '糬', + '' => '糪', + '' => '繶', + '' => '繵', + '' => '繸', + '' => '繰', + '' => '繷', + '' => '繯', + '' => '繺', + '' => '繲', + '' => '繴', + '' => '繨', + '' => '罋', + '' => '罊', + '' => '羃', + '' => '羆', + '' => '羷', + '' => '翽', + '' => '翾', + '' => '聸', + '' => '臗', + '' => '臕', + '' => '艤', + '' => '艡', + '' => '艣', + '' => '藫', + '' => '藱', + '' => '藭', + '' => '藙', + '' => '藡', + '' => '藨', + '' => '藚', + '' => '藗', + '' => '藬', + '' => '藲', + '' => '藸', + '' => '藘', + '' => '藟', + '' => '藣', + '' => '藜', + '' => '藑', + '' => '藰', + '' => '藦', + '' => '藯', + '' => '藞', + '' => '藢', + '' => '蠀', + '' => '蟺', + '' => '蠃', + '' => '蟶', + '' => '蟷', + '' => '蠉', + '' => '蠌', + '' => '蠋', + '' => '蠆', + '' => '蟼', + '' => '蠈', + '' => '蟿', + '' => '蠊', + '' => '蠂', + '' => '襢', + '' => '襚', + '' => '襛', + '' => '襗', + '' => '襡', + '' => '襜', + '' => '襘', + '' => '襝', + '' => '襙', + '' => '覈', + '' => '覷', + '' => '覶', + '' => '觶', + '' => '譐', + '' => '譈', + '' => '譊', + '' => '譀', + '' => '譓', + '' => '譖', + '' => '譔', + '' => '譋', + '' => '譕', + '@' => '譑', + 'A' => '譂', + 'B' => '譒', + 'C' => '譗', + 'D' => '豃', + 'E' => '豷', + 'F' => '豶', + 'G' => '貚', + 'H' => '贆', + 'I' => '贇', + 'J' => '贉', + 'K' => '趬', + 'L' => '趪', + 'M' => '趭', + 'N' => '趫', + 'O' => '蹭', + 'P' => '蹸', + 'Q' => '蹳', + 'R' => '蹪', + 'S' => '蹯', + 'T' => '蹻', + 'U' => '軂', + 'V' => '轒', + 'W' => '轑', + 'X' => '轏', + 'Y' => '轐', + 'Z' => '轓', + '[' => '辴', + '\\' => '酀', + ']' => '鄿', + '^' => '醰', + '_' => '醭', + '`' => '鏞', + 'a' => '鏇', + 'b' => '鏏', + 'c' => '鏂', + 'd' => '鏚', + 'e' => '鏐', + 'f' => '鏹', + 'g' => '鏬', + 'h' => '鏌', + 'i' => '鏙', + 'j' => '鎩', + 'k' => '鏦', + 'l' => '鏊', + 'm' => '鏔', + 'n' => '鏮', + 'o' => '鏣', + 'p' => '鏕', + 'q' => '鏄', + 'r' => '鏎', + 's' => '鏀', + 't' => '鏒', + 'u' => '鏧', + 'v' => '镽', + 'w' => '闚', + 'x' => '闛', + 'y' => '雡', + 'z' => '霩', + '{' => '霫', + '|' => '霬', + '}' => '霨', + '~' => '霦', + '' => '鞳', + '' => '鞷', + '' => '鞶', + '' => '韝', + '' => '韞', + '' => '韟', + '' => '顜', + '' => '顙', + '' => '顝', + '' => '顗', + '' => '颿', + '' => '颽', + '' => '颻', + '' => '颾', + '' => '饈', + '' => '饇', + '' => '饃', + '' => '馦', + '' => '馧', + '' => '騚', + '' => '騕', + '' => '騥', + '' => '騝', + '' => '騤', + '' => '騛', + '' => '騢', + '' => '騠', + '' => '騧', + '' => '騣', + '' => '騞', + '' => '騜', + '' => '騔', + '' => '髂', + '' => '鬋', + '' => '鬊', + '' => '鬎', + '' => '鬌', + '' => '鬷', + '' => '鯪', + '' => '鯫', + '' => '鯠', + '' => '鯞', + '' => '鯤', + '' => '鯦', + '' => '鯢', + '' => '鯰', + '' => '鯔', + '' => '鯗', + '' => '鯬', + '' => '鯜', + '' => '鯙', + '' => '鯥', + '' => '鯕', + '' => '鯡', + '' => '鯚', + '' => '鵷', + '' => '鶁', + '' => '鶊', + '' => '鶄', + '' => '鶈', + '' => '鵱', + '' => '鶀', + '' => '鵸', + '' => '鶆', + '' => '鶋', + '' => '鶌', + '' => '鵽', + '' => '鵫', + '' => '鵴', + '' => '鵵', + '' => '鵰', + '' => '鵩', + '' => '鶅', + '' => '鵳', + '' => '鵻', + '' => '鶂', + '' => '鵯', + '' => '鵹', + '' => '鵿', + '' => '鶇', + '' => '鵨', + '' => '麔', + '' => '麑', + '' => '黀', + '' => '黼', + '' => '鼭', + '' => '齀', + '' => '齁', + '' => '齍', + '' => '齖', + '' => '齗', + '' => '齘', + '' => '匷', + '' => '嚲', + '@' => '嚵', + 'A' => '嚳', + 'B' => '壣', + 'C' => '孅', + 'D' => '巆', + 'E' => '巇', + 'F' => '廮', + 'G' => '廯', + 'H' => '忀', + 'I' => '忁', + 'J' => '懹', + 'K' => '攗', + 'L' => '攖', + 'M' => '攕', + 'N' => '攓', + 'O' => '旟', + 'P' => '曨', + 'Q' => '曣', + 'R' => '曤', + 'S' => '櫳', + 'T' => '櫰', + 'U' => '櫪', + 'V' => '櫨', + 'W' => '櫹', + 'X' => '櫱', + 'Y' => '櫮', + 'Z' => '櫯', + '[' => '瀼', + '\\' => '瀵', + ']' => '瀯', + '^' => '瀷', + '_' => '瀴', + '`' => '瀱', + 'a' => '灂', + 'b' => '瀸', + 'c' => '瀿', + 'd' => '瀺', + 'e' => '瀹', + 'f' => '灀', + 'g' => '瀻', + 'h' => '瀳', + 'i' => '灁', + 'j' => '爓', + 'k' => '爔', + 'l' => '犨', + 'm' => '獽', + 'n' => '獼', + 'o' => '璺', + 'p' => '皫', + 'q' => '皪', + 'r' => '皾', + 's' => '盭', + 't' => '矌', + 'u' => '矎', + 'v' => '矏', + 'w' => '矍', + 'x' => '矲', + 'y' => '礥', + 'z' => '礣', + '{' => '礧', + '|' => '礨', + '}' => '礤', + '~' => '礩', + '' => '禲', + '' => '穮', + '' => '穬', + '' => '穭', + '' => '竷', + '' => '籉', + '' => '籈', + '' => '籊', + '' => '籇', + '' => '籅', + '' => '糮', + '' => '繻', + '' => '繾', + '' => '纁', + '' => '纀', + '' => '羺', + '' => '翿', + '' => '聹', + '' => '臛', + '' => '臙', + '' => '舋', + '' => '艨', + '' => '艩', + '' => '蘢', + '' => '藿', + '' => '蘁', + '' => '藾', + '' => '蘛', + '' => '蘀', + '' => '藶', + '' => '蘄', + '' => '蘉', + '' => '蘅', + '' => '蘌', + '' => '藽', + '' => '蠙', + '' => '蠐', + '' => '蠑', + '' => '蠗', + '' => '蠓', + '' => '蠖', + '' => '襣', + '' => '襦', + '' => '覹', + '' => '觷', + '' => '譠', + '' => '譪', + '' => '譝', + '' => '譨', + '' => '譣', + '' => '譥', + '' => '譧', + '' => '譭', + '' => '趮', + '' => '躆', + '' => '躈', + '' => '躄', + '' => '轙', + '' => '轖', + '' => '轗', + '' => '轕', + '' => '轘', + '' => '轚', + '' => '邍', + '' => '酃', + '' => '酁', + '' => '醷', + '' => '醵', + '' => '醲', + '' => '醳', + '' => '鐋', + '' => '鐓', + '' => '鏻', + '' => '鐠', + '' => '鐏', + '' => '鐔', + '' => '鏾', + '' => '鐕', + '' => '鐐', + '' => '鐨', + '' => '鐙', + '' => '鐍', + '' => '鏵', + '' => '鐀', + '' => '鏷', + '' => '鐇', + '' => '鐎', + '' => '鐖', + '' => '鐒', + '' => '鏺', + '' => '鐉', + '' => '鏸', + '' => '鐊', + '' => '鏿', + '@' => '鏼', + 'A' => '鐌', + 'B' => '鏶', + 'C' => '鐑', + 'D' => '鐆', + 'E' => '闞', + 'F' => '闠', + 'G' => '闟', + 'H' => '霮', + 'I' => '霯', + 'J' => '鞹', + 'K' => '鞻', + 'L' => '韽', + 'M' => '韾', + 'N' => '顠', + 'O' => '顢', + 'P' => '顣', + 'Q' => '顟', + 'R' => '飁', + 'S' => '飂', + 'T' => '饐', + 'U' => '饎', + 'V' => '饙', + 'W' => '饌', + 'X' => '饋', + 'Y' => '饓', + 'Z' => '騲', + '[' => '騴', + '\\' => '騱', + ']' => '騬', + '^' => '騪', + '_' => '騶', + '`' => '騩', + 'a' => '騮', + 'b' => '騸', + 'c' => '騭', + 'd' => '髇', + 'e' => '髊', + 'f' => '髆', + 'g' => '鬐', + 'h' => '鬒', + 'i' => '鬑', + 'j' => '鰋', + 'k' => '鰈', + 'l' => '鯷', + 'm' => '鰅', + 'n' => '鰒', + 'o' => '鯸', + 'p' => '鱀', + 'q' => '鰇', + 'r' => '鰎', + 's' => '鰆', + 't' => '鰗', + 'u' => '鰔', + 'v' => '鰉', + 'w' => '鶟', + 'x' => '鶙', + 'y' => '鶤', + 'z' => '鶝', + '{' => '鶒', + '|' => '鶘', + '}' => '鶐', + '~' => '鶛', + '' => '鶠', + '' => '鶔', + '' => '鶜', + '' => '鶪', + '' => '鶗', + '' => '鶡', + '' => '鶚', + '' => '鶢', + '' => '鶨', + '' => '鶞', + '' => '鶣', + '' => '鶿', + '' => '鶩', + '' => '鶖', + '' => '鶦', + '' => '鶧', + '' => '麙', + '' => '麛', + '' => '麚', + '' => '黥', + '' => '黤', + '' => '黧', + '' => '黦', + '' => '鼰', + '' => '鼮', + '' => '齛', + '' => '齠', + '' => '齞', + '' => '齝', + '' => '齙', + '' => '龑', + '' => '儺', + '' => '儹', + '' => '劘', + '' => '劗', + '' => '囃', + '' => '嚽', + '' => '嚾', + '' => '孈', + '' => '孇', + '' => '巋', + '' => '巏', + '' => '廱', + '' => '懽', + '' => '攛', + '' => '欂', + '' => '櫼', + '' => '欃', + '' => '櫸', + '' => '欀', + '' => '灃', + '' => '灄', + '' => '灊', + '' => '灈', + '' => '灉', + '' => '灅', + '' => '灆', + '' => '爝', + '' => '爚', + '' => '爙', + '' => '獾', + '' => '甗', + '' => '癪', + '' => '矐', + '' => '礭', + '' => '礱', + '' => '礯', + '' => '籔', + '' => '籓', + '' => '糲', + '' => '纊', + '' => '纇', + '' => '纈', + '' => '纋', + '' => '纆', + '' => '纍', + '' => '罍', + '' => '羻', + '' => '耰', + '' => '臝', + '' => '蘘', + '' => '蘪', + '' => '蘦', + '' => '蘟', + '' => '蘣', + '' => '蘜', + '' => '蘙', + '' => '蘧', + '' => '蘮', + '' => '蘡', + '' => '蘠', + '' => '蘩', + '' => '蘞', + '' => '蘥', + '@' => '蠩', + 'A' => '蠝', + 'B' => '蠛', + 'C' => '蠠', + 'D' => '蠤', + 'E' => '蠜', + 'F' => '蠫', + 'G' => '衊', + 'H' => '襭', + 'I' => '襩', + 'J' => '襮', + 'K' => '襫', + 'L' => '觺', + 'M' => '譹', + 'N' => '譸', + 'O' => '譅', + 'P' => '譺', + 'Q' => '譻', + 'R' => '贐', + 'S' => '贔', + 'T' => '趯', + 'U' => '躎', + 'V' => '躌', + 'W' => '轞', + 'X' => '轛', + 'Y' => '轝', + 'Z' => '酆', + '[' => '酄', + '\\' => '酅', + ']' => '醹', + '^' => '鐿', + '_' => '鐻', + '`' => '鐶', + 'a' => '鐩', + 'b' => '鐽', + 'c' => '鐼', + 'd' => '鐰', + 'e' => '鐹', + 'f' => '鐪', + 'g' => '鐷', + 'h' => '鐬', + 'i' => '鑀', + 'j' => '鐱', + 'k' => '闥', + 'l' => '闤', + 'm' => '闣', + 'n' => '霵', + 'o' => '霺', + 'p' => '鞿', + 'q' => '韡', + 'r' => '顤', + 's' => '飉', + 't' => '飆', + 'u' => '飀', + 'v' => '饘', + 'w' => '饖', + 'x' => '騹', + 'y' => '騽', + 'z' => '驆', + '{' => '驄', + '|' => '驂', + '}' => '驁', + '~' => '騺', + '' => '騿', + '' => '髍', + '' => '鬕', + '' => '鬗', + '' => '鬘', + '' => '鬖', + '' => '鬺', + '' => '魒', + '' => '鰫', + '' => '鰝', + '' => '鰜', + '' => '鰬', + '' => '鰣', + '' => '鰨', + '' => '鰩', + '' => '鰤', + '' => '鰡', + '' => '鶷', + '' => '鶶', + '' => '鶼', + '' => '鷁', + '' => '鷇', + '' => '鷊', + '' => '鷏', + '' => '鶾', + '' => '鷅', + '' => '鷃', + '' => '鶻', + '' => '鶵', + '' => '鷎', + '' => '鶹', + '' => '鶺', + '' => '鶬', + '' => '鷈', + '' => '鶱', + '' => '鶭', + '' => '鷌', + '' => '鶳', + '' => '鷍', + '' => '鶲', + '' => '鹺', + '' => '麜', + '' => '黫', + '' => '黮', + '' => '黭', + '' => '鼛', + '' => '鼘', + '' => '鼚', + '' => '鼱', + '' => '齎', + '' => '齥', + '' => '齤', + '' => '龒', + '' => '亹', + '' => '囆', + '' => '囅', + '' => '囋', + '' => '奱', + '' => '孋', + '' => '孌', + '' => '巕', + '' => '巑', + '' => '廲', + '' => '攡', + '' => '攠', + '' => '攦', + '' => '攢', + '' => '欋', + '' => '欈', + '' => '欉', + '' => '氍', + '' => '灕', + '' => '灖', + '' => '灗', + '' => '灒', + '' => '爞', + '' => '爟', + '' => '犩', + '' => '獿', + '' => '瓘', + '' => '瓕', + '' => '瓙', + '' => '瓗', + '' => '癭', + '' => '皭', + '' => '礵', + '' => '禴', + '' => '穰', + '' => '穱', + '' => '籗', + '' => '籜', + '' => '籙', + '' => '籛', + '' => '籚', + '@' => '糴', + 'A' => '糱', + 'B' => '纑', + 'C' => '罏', + 'D' => '羇', + 'E' => '臞', + 'F' => '艫', + 'G' => '蘴', + 'H' => '蘵', + 'I' => '蘳', + 'J' => '蘬', + 'K' => '蘲', + 'L' => '蘶', + 'M' => '蠬', + 'N' => '蠨', + 'O' => '蠦', + 'P' => '蠪', + 'Q' => '蠥', + 'R' => '襱', + 'S' => '覿', + 'T' => '覾', + 'U' => '觻', + 'V' => '譾', + 'W' => '讄', + 'X' => '讂', + 'Y' => '讆', + 'Z' => '讅', + '[' => '譿', + '\\' => '贕', + ']' => '躕', + '^' => '躔', + '_' => '躚', + '`' => '躒', + 'a' => '躐', + 'b' => '躖', + 'c' => '躗', + 'd' => '轠', + 'e' => '轢', + 'f' => '酇', + 'g' => '鑌', + 'h' => '鑐', + 'i' => '鑊', + 'j' => '鑋', + 'k' => '鑏', + 'l' => '鑇', + 'm' => '鑅', + 'n' => '鑈', + 'o' => '鑉', + 'p' => '鑆', + 'q' => '霿', + 'r' => '韣', + 's' => '顪', + 't' => '顩', + 'u' => '飋', + 'v' => '饔', + 'w' => '饛', + 'x' => '驎', + 'y' => '驓', + 'z' => '驔', + '{' => '驌', + '|' => '驏', + '}' => '驈', + '~' => '驊', + '' => '驉', + '' => '驒', + '' => '驐', + '' => '髐', + '' => '鬙', + '' => '鬫', + '' => '鬻', + '' => '魖', + '' => '魕', + '' => '鱆', + '' => '鱈', + '' => '鰿', + '' => '鱄', + '' => '鰹', + '' => '鰳', + '' => '鱁', + '' => '鰼', + '' => '鰷', + '' => '鰴', + '' => '鰲', + '' => '鰽', + '' => '鰶', + '' => '鷛', + '' => '鷒', + '' => '鷞', + '' => '鷚', + '' => '鷋', + '' => '鷐', + '' => '鷜', + '' => '鷑', + '' => '鷟', + '' => '鷩', + '' => '鷙', + '' => '鷘', + '' => '鷖', + '' => '鷵', + '' => '鷕', + '' => '鷝', + '' => '麶', + '' => '黰', + '' => '鼵', + '' => '鼳', + '' => '鼲', + '' => '齂', + '' => '齫', + '' => '龕', + '' => '龢', + '' => '儽', + '' => '劙', + '' => '壨', + '' => '壧', + '' => '奲', + '' => '孍', + '' => '巘', + '' => '蠯', + '' => '彏', + '' => '戁', + '' => '戃', + '' => '戄', + '' => '攩', + '' => '攥', + '' => '斖', + '' => '曫', + '' => '欑', + '' => '欒', + '' => '欏', + '' => '毊', + '' => '灛', + '' => '灚', + '' => '爢', + '' => '玂', + '' => '玁', + '' => '玃', + '' => '癰', + '' => '矔', + '' => '籧', + '' => '籦', + '' => '纕', + '' => '艬', + '' => '蘺', + '' => '虀', + '' => '蘹', + '' => '蘼', + '' => '蘱', + '' => '蘻', + '' => '蘾', + '' => '蠰', + '' => '蠲', + '' => '蠮', + '' => '蠳', + '' => '襶', + '' => '襴', + '' => '襳', + '' => '觾', + '@' => '讌', + 'A' => '讎', + 'B' => '讋', + 'C' => '讈', + 'D' => '豅', + 'E' => '贙', + 'F' => '躘', + 'G' => '轤', + 'H' => '轣', + 'I' => '醼', + 'J' => '鑢', + 'K' => '鑕', + 'L' => '鑝', + 'M' => '鑗', + 'N' => '鑞', + 'O' => '韄', + 'P' => '韅', + 'Q' => '頀', + 'R' => '驖', + 'S' => '驙', + 'T' => '鬞', + 'U' => '鬟', + 'V' => '鬠', + 'W' => '鱒', + 'X' => '鱘', + 'Y' => '鱐', + 'Z' => '鱊', + '[' => '鱍', + '\\' => '鱋', + ']' => '鱕', + '^' => '鱙', + '_' => '鱌', + '`' => '鱎', + 'a' => '鷻', + 'b' => '鷷', + 'c' => '鷯', + 'd' => '鷣', + 'e' => '鷫', + 'f' => '鷸', + 'g' => '鷤', + 'h' => '鷶', + 'i' => '鷡', + 'j' => '鷮', + 'k' => '鷦', + 'l' => '鷲', + 'm' => '鷰', + 'n' => '鷢', + 'o' => '鷬', + 'p' => '鷴', + 'q' => '鷳', + 'r' => '鷨', + 's' => '鷭', + 't' => '黂', + 'u' => '黐', + 'v' => '黲', + 'w' => '黳', + 'x' => '鼆', + 'y' => '鼜', + 'z' => '鼸', + '{' => '鼷', + '|' => '鼶', + '}' => '齃', + '~' => '齏', + '' => '齱', + '' => '齰', + '' => '齮', + '' => '齯', + '' => '囓', + '' => '囍', + '' => '孎', + '' => '屭', + '' => '攭', + '' => '曭', + '' => '曮', + '' => '欓', + '' => '灟', + '' => '灡', + '' => '灝', + '' => '灠', + '' => '爣', + '' => '瓛', + '' => '瓥', + '' => '矕', + '' => '礸', + '' => '禷', + '' => '禶', + '' => '籪', + '' => '纗', + '' => '羉', + '' => '艭', + '' => '虃', + '' => '蠸', + '' => '蠷', + '' => '蠵', + '' => '衋', + '' => '讔', + '' => '讕', + '' => '躞', + '' => '躟', + '' => '躠', + '' => '躝', + '' => '醾', + '' => '醽', + '' => '釂', + '' => '鑫', + '' => '鑨', + '' => '鑩', + '' => '雥', + '' => '靆', + '' => '靃', + '' => '靇', + '' => '韇', + '' => '韥', + '' => '驞', + '' => '髕', + '' => '魙', + '' => '鱣', + '' => '鱧', + '' => '鱦', + '' => '鱢', + '' => '鱞', + '' => '鱠', + '' => '鸂', + '' => '鷾', + '' => '鸇', + '' => '鸃', + '' => '鸆', + '' => '鸅', + '' => '鸀', + '' => '鸁', + '' => '鸉', + '' => '鷿', + '' => '鷽', + '' => '鸄', + '' => '麠', + '' => '鼞', + '' => '齆', + '' => '齴', + '' => '齵', + '' => '齶', + '' => '囔', + '' => '攮', + '' => '斸', + '' => '欘', + '' => '欙', + '' => '欗', + '' => '欚', + '' => '灢', + '' => '爦', + '' => '犪', + '' => '矘', + '' => '矙', + '' => '礹', + '' => '籩', + '' => '籫', + '' => '糶', + '' => '纚', + '@' => '纘', + 'A' => '纛', + 'B' => '纙', + 'C' => '臠', + 'D' => '臡', + 'E' => '虆', + 'F' => '虇', + 'G' => '虈', + 'H' => '襹', + 'I' => '襺', + 'J' => '襼', + 'K' => '襻', + 'L' => '觿', + 'M' => '讘', + 'N' => '讙', + 'O' => '躥', + 'P' => '躤', + 'Q' => '躣', + 'R' => '鑮', + 'S' => '鑭', + 'T' => '鑯', + 'U' => '鑱', + 'V' => '鑳', + 'W' => '靉', + 'X' => '顲', + 'Y' => '饟', + 'Z' => '鱨', + '[' => '鱮', + '\\' => '鱭', + ']' => '鸋', + '^' => '鸍', + '_' => '鸐', + '`' => '鸏', + 'a' => '鸒', + 'b' => '鸑', + 'c' => '麡', + 'd' => '黵', + 'e' => '鼉', + 'f' => '齇', + 'g' => '齸', + 'h' => '齻', + 'i' => '齺', + 'j' => '齹', + 'k' => '圞', + 'l' => '灦', + 'm' => '籯', + 'n' => '蠼', + 'o' => '趲', + 'p' => '躦', + 'q' => '釃', + 'r' => '鑴', + 's' => '鑸', + 't' => '鑶', + 'u' => '鑵', + 'v' => '驠', + 'w' => '鱴', + 'x' => '鱳', + 'y' => '鱱', + 'z' => '鱵', + '{' => '鸔', + '|' => '鸓', + '}' => '黶', + '~' => '鼊', + '' => '龤', + '' => '灨', + '' => '灥', + '' => '糷', + '' => '虪', + '' => '蠾', + '' => '蠽', + '' => '蠿', + '' => '讞', + '' => '貜', + '' => '躩', + '' => '軉', + '' => '靋', + '' => '顳', + '' => '顴', + '' => '飌', + '' => '饡', + '' => '馫', + '' => '驤', + '' => '驦', + '' => '驧', + '' => '鬤', + '' => '鸕', + '' => '鸗', + '' => '齈', + '' => '戇', + '' => '欞', + '' => '爧', + '' => '虌', + '' => '躨', + '' => '钂', + '' => '钀', + '' => '钁', + '' => '驩', + '' => '驨', + '' => '鬮', + '' => '鸙', + '' => '爩', + '' => '虋', + '' => '讟', + '' => '钃', + '' => '鱹', + '' => '麷', + '' => '癵', + '' => '驫', + '' => '鱺', + '' => '鸝', + '' => '灩', + '' => '灪', + '' => '麤', + '' => '齾', + '' => '齉', + '' => '龘', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp037.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp037.php new file mode 100644 index 0000000..a014e4b Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp037.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1006.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1006.php new file mode 100644 index 0000000..2b5e7be Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1006.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1026.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1026.php new file mode 100644 index 0000000..aba455b Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp1026.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp424.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp424.php new file mode 100644 index 0000000..e8e2370 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp424.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp437.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp437.php new file mode 100644 index 0000000..e3ebb45 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp437.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp500.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp500.php new file mode 100644 index 0000000..3771c8f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp500.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp737.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp737.php new file mode 100644 index 0000000..2d67d33 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp737.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp775.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp775.php new file mode 100644 index 0000000..1fbc4cd Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp775.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp850.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp850.php new file mode 100644 index 0000000..0b314c8 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp850.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp852.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp852.php new file mode 100644 index 0000000..f8c318c Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp852.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp855.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp855.php new file mode 100644 index 0000000..48440ba Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp855.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp856.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp856.php new file mode 100644 index 0000000..c9cac0c Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp856.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp857.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp857.php new file mode 100644 index 0000000..3e7770a Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp857.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp860.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp860.php new file mode 100644 index 0000000..2a52d47 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp860.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp861.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp861.php new file mode 100644 index 0000000..4ba6573 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp861.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp862.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp862.php new file mode 100644 index 0000000..d2a29a2 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp862.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp863.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp863.php new file mode 100644 index 0000000..1f36b9a Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp863.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp864.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp864.php new file mode 100644 index 0000000..953e463 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp864.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp865.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp865.php new file mode 100644 index 0000000..2668bcc Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp865.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp866.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp866.php new file mode 100644 index 0000000..a7b47f8 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp866.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp869.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp869.php new file mode 100644 index 0000000..0f04054 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp869.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp874.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp874.php new file mode 100644 index 0000000..4799456 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp874.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp875.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp875.php new file mode 100644 index 0000000..8561645 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp875.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp932.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp932.php new file mode 100644 index 0000000..0bf828f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp932.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp936.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp936.php new file mode 100644 index 0000000..a593d05 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp936.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp949.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp949.php new file mode 100644 index 0000000..d4e99f1 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp949.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp950.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp950.php new file mode 100644 index 0000000..267b190 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.cp950.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-1.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-1.php new file mode 100644 index 0000000..d7a217c Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-1.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-10.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-10.php new file mode 100644 index 0000000..d60f647 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-10.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-11.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-11.php new file mode 100644 index 0000000..d69220b Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-11.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-13.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-13.php new file mode 100644 index 0000000..838783f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-13.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-14.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-14.php new file mode 100644 index 0000000..65a48ee Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-14.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-15.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-15.php new file mode 100644 index 0000000..42e50e0 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-15.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-16.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-16.php new file mode 100644 index 0000000..46758a6 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-16.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-2.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-2.php new file mode 100644 index 0000000..5f23f51 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-2.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-3.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-3.php new file mode 100644 index 0000000..b31bb83 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-3.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-4.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-4.php new file mode 100644 index 0000000..9cbf9f3 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-4.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-5.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-5.php new file mode 100644 index 0000000..fd03882 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-5.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-6.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-6.php new file mode 100644 index 0000000..ed6f72f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-6.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-7.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-7.php new file mode 100644 index 0000000..cf723ac Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-7.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-8.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-8.php new file mode 100644 index 0000000..c978731 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-8.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-9.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-9.php new file mode 100644 index 0000000..2a3e36a Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.iso-8859-9.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-r.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-r.php new file mode 100644 index 0000000..d83c212 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-r.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-u.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-u.php new file mode 100644 index 0000000..dbbf96b Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.koi8-u.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.us-ascii.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.us-ascii.php new file mode 100644 index 0000000..94a93b2 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.us-ascii.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1250.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1250.php new file mode 100644 index 0000000..d1d5e6f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1250.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1251.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1251.php new file mode 100644 index 0000000..f422a71 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1251.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1252.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1252.php new file mode 100644 index 0000000..ba6d203 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1252.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1253.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1253.php new file mode 100644 index 0000000..c04dc8f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1253.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1254.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1254.php new file mode 100644 index 0000000..1cfadcf Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1254.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1255.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1255.php new file mode 100644 index 0000000..f73cbb6 Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1255.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1256.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1256.php new file mode 100644 index 0000000..953704f Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1256.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1257.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1257.php new file mode 100644 index 0000000..78580ec Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1257.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1258.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1258.php new file mode 100644 index 0000000..de1609d Binary files /dev/null and b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/from.windows-1258.php differ diff --git a/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/translit.php b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/translit.php new file mode 100644 index 0000000..779db64 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/Resources/charset/translit.php @@ -0,0 +1,4103 @@ + 'μ', + '¼' => ' 1⁄4 ', + '½' => ' 1⁄2 ', + '¾' => ' 3⁄4 ', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ŀ' => 'L·', + 'ŀ' => 'l·', + 'ʼn' => 'ʼn', + 'ſ' => 's', + 'DŽ' => 'DŽ', + 'Dž' => 'Dž', + 'dž' => 'dž', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϒ' => 'Υ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϲ' => 'ς', + 'ϴ' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'և' => 'եւ', + 'ٵ' => 'اٴ', + 'ٶ' => 'وٴ', + 'ٷ' => 'ۇٴ', + 'ٸ' => 'يٴ', + 'ำ' => 'ํา', + 'ຳ' => 'ໍາ', + 'ໜ' => 'ຫນ', + 'ໝ' => 'ຫມ', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ẚ' => 'aʾ', + '․' => '.', + '‥' => '..', + '…' => '...', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '′′′′', + '₨' => 'Rs', + '℀' => 'a/c', + '℁' => 'a/s', + 'ℂ' => 'C', + '℃' => '°C', + '℅' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Ɛ', + '℉' => '°F', + 'ℊ' => 'g', + 'ℋ' => 'H', + 'ℌ' => 'H', + 'ℍ' => 'H', + 'ℎ' => 'h', + 'ℏ' => 'ħ', + 'ℐ' => 'I', + 'ℑ' => 'I', + 'ℒ' => 'L', + 'ℓ' => 'l', + 'ℕ' => 'N', + '№' => 'No', + 'ℙ' => 'P', + 'ℚ' => 'Q', + 'ℛ' => 'R', + 'ℜ' => 'R', + 'ℝ' => 'R', + '℡' => 'TEL', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'ℭ' => 'C', + 'ℯ' => 'e', + 'ℰ' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'ℴ' => 'o', + 'ℵ' => 'א', + 'ℶ' => 'ב', + 'ℷ' => 'ג', + 'ℸ' => 'ד', + 'ℹ' => 'i', + '℻' => 'FAX', + 'ℼ' => 'π', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'ℿ' => 'Π', + '⅀' => '∑', + 'ⅅ' => 'D', + 'ⅆ' => 'd', + 'ⅇ' => 'e', + 'ⅈ' => 'i', + 'ⅉ' => 'j', + '⅐' => ' 1⁄7 ', + '⅑' => ' 1⁄9 ', + '⅒' => ' 1⁄10 ', + '⅓' => ' 1⁄3 ', + '⅔' => ' 2⁄3 ', + '⅕' => ' 1⁄5 ', + '⅖' => ' 2⁄5 ', + '⅗' => ' 3⁄5 ', + '⅘' => ' 4⁄5 ', + '⅙' => ' 1⁄6 ', + '⅚' => ' 5⁄6 ', + '⅛' => ' 1⁄8 ', + '⅜' => ' 3⁄8 ', + '⅝' => ' 5⁄8 ', + '⅞' => ' 7⁄8 ', + '⅟' => ' 1⁄ ', + 'Ⅰ' => 'I', + 'Ⅱ' => 'II', + 'Ⅲ' => 'III', + 'Ⅳ' => 'IV', + 'Ⅴ' => 'V', + 'Ⅵ' => 'VI', + 'Ⅶ' => 'VII', + 'Ⅷ' => 'VIII', + 'Ⅸ' => 'IX', + 'Ⅹ' => 'X', + 'Ⅺ' => 'XI', + 'Ⅻ' => 'XII', + 'Ⅼ' => 'L', + 'Ⅽ' => 'C', + 'Ⅾ' => 'D', + 'Ⅿ' => 'M', + 'ⅰ' => 'i', + 'ⅱ' => 'ii', + 'ⅲ' => 'iii', + 'ⅳ' => 'iv', + 'ⅴ' => 'v', + 'ⅵ' => 'vi', + 'ⅶ' => 'vii', + 'ⅷ' => 'viii', + 'ⅸ' => 'ix', + 'ⅹ' => 'x', + 'ⅺ' => 'xi', + 'ⅻ' => 'xii', + 'ⅼ' => 'l', + 'ⅽ' => 'c', + 'ⅾ' => 'd', + 'ⅿ' => 'm', + '↉' => ' 0⁄3 ', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + '①' => '(1)', + '②' => '(2)', + '③' => '(3)', + '④' => '(4)', + '⑤' => '(5)', + '⑥' => '(6)', + '⑦' => '(7)', + '⑧' => '(8)', + '⑨' => '(9)', + '⑩' => '(10)', + '⑪' => '(11)', + '⑫' => '(12)', + '⑬' => '(13)', + '⑭' => '(14)', + '⑮' => '(15)', + '⑯' => '(16)', + '⑰' => '(17)', + '⑱' => '(18)', + '⑲' => '(19)', + '⑳' => '(20)', + '⑴' => '(1)', + '⑵' => '(2)', + '⑶' => '(3)', + '⑷' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + '⑻' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + '⑿' => '(12)', + '⒀' => '(13)', + '⒁' => '(14)', + '⒂' => '(15)', + '⒃' => '(16)', + '⒄' => '(17)', + '⒅' => '(18)', + '⒆' => '(19)', + '⒇' => '(20)', + '⒈' => '1.', + '⒉' => '2.', + '⒊' => '3.', + '⒋' => '4.', + '⒌' => '5.', + '⒍' => '6.', + '⒎' => '7.', + '⒏' => '8.', + '⒐' => '9.', + '⒑' => '10.', + '⒒' => '11.', + '⒓' => '12.', + '⒔' => '13.', + '⒕' => '14.', + '⒖' => '15.', + '⒗' => '16.', + '⒘' => '17.', + '⒙' => '18.', + '⒚' => '19.', + '⒛' => '20.', + '⒜' => '(a)', + '⒝' => '(b)', + '⒞' => '(c)', + '⒟' => '(d)', + '⒠' => '(e)', + '⒡' => '(f)', + '⒢' => '(g)', + '⒣' => '(h)', + '⒤' => '(i)', + '⒥' => '(j)', + '⒦' => '(k)', + '⒧' => '(l)', + '⒨' => '(m)', + '⒩' => '(n)', + '⒪' => '(o)', + '⒫' => '(p)', + '⒬' => '(q)', + '⒭' => '(r)', + '⒮' => '(s)', + '⒯' => '(t)', + '⒰' => '(u)', + '⒱' => '(v)', + '⒲' => '(w)', + '⒳' => '(x)', + '⒴' => '(y)', + '⒵' => '(z)', + 'Ⓐ' => '(A)', + 'Ⓑ' => '(B)', + 'Ⓒ' => '(C)', + 'Ⓓ' => '(D)', + 'Ⓔ' => '(E)', + 'Ⓕ' => '(F)', + 'Ⓖ' => '(G)', + 'Ⓗ' => '(H)', + 'Ⓘ' => '(I)', + 'Ⓙ' => '(J)', + 'Ⓚ' => '(K)', + 'Ⓛ' => '(L)', + 'Ⓜ' => '(M)', + 'Ⓝ' => '(N)', + 'Ⓞ' => '(O)', + 'Ⓟ' => '(P)', + 'Ⓠ' => '(Q)', + 'Ⓡ' => '(R)', + 'Ⓢ' => '(S)', + 'Ⓣ' => '(T)', + 'Ⓤ' => '(U)', + 'Ⓥ' => '(V)', + 'Ⓦ' => '(W)', + 'Ⓧ' => '(X)', + 'Ⓨ' => '(Y)', + 'Ⓩ' => '(Z)', + 'ⓐ' => '(a)', + 'ⓑ' => '(b)', + 'ⓒ' => '(c)', + 'ⓓ' => '(d)', + 'ⓔ' => '(e)', + 'ⓕ' => '(f)', + 'ⓖ' => '(g)', + 'ⓗ' => '(h)', + 'ⓘ' => '(i)', + 'ⓙ' => '(j)', + 'ⓚ' => '(k)', + 'ⓛ' => '(l)', + 'ⓜ' => '(m)', + 'ⓝ' => '(n)', + 'ⓞ' => '(o)', + 'ⓟ' => '(p)', + 'ⓠ' => '(q)', + 'ⓡ' => '(r)', + 'ⓢ' => '(s)', + 'ⓣ' => '(t)', + 'ⓤ' => '(u)', + 'ⓥ' => '(v)', + 'ⓦ' => '(w)', + 'ⓧ' => '(x)', + 'ⓨ' => '(y)', + 'ⓩ' => '(z)', + '⓪' => '(0)', + '⨌' => '∫∫∫∫', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + '⺟' => '母', + '⻳' => '龟', + '⼀' => '一', + '⼁' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => '乙', + '⼅' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => '儿', + '⼊' => '入', + '⼋' => '八', + '⼌' => '冂', + '⼍' => '冖', + '⼎' => '冫', + '⼏' => '几', + '⼐' => '凵', + '⼑' => '刀', + '⼒' => '力', + '⼓' => '勹', + '⼔' => '匕', + '⼕' => '匚', + '⼖' => '匸', + '⼗' => '十', + '⼘' => '卜', + '⼙' => '卩', + '⼚' => '厂', + '⼛' => '厶', + '⼜' => '又', + '⼝' => '口', + '⼞' => '囗', + '⼟' => '土', + '⼠' => '士', + '⼡' => '夂', + '⼢' => '夊', + '⼣' => '夕', + '⼤' => '大', + '⼥' => '女', + '⼦' => '子', + '⼧' => '宀', + '⼨' => '寸', + '⼩' => '小', + '⼪' => '尢', + '⼫' => '尸', + '⼬' => '屮', + '⼭' => '山', + '⼮' => '巛', + '⼯' => '工', + '⼰' => '己', + '⼱' => '巾', + '⼲' => '干', + '⼳' => '幺', + '⼴' => '广', + '⼵' => '廴', + '⼶' => '廾', + '⼷' => '弋', + '⼸' => '弓', + '⼹' => '彐', + '⼺' => '彡', + '⼻' => '彳', + '⼼' => '心', + '⼽' => '戈', + '⼾' => '戶', + '⼿' => '手', + '⽀' => '支', + '⽁' => '攴', + '⽂' => '文', + '⽃' => '斗', + '⽄' => '斤', + '⽅' => '方', + '⽆' => '无', + '⽇' => '日', + '⽈' => '曰', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => '止', + '⽍' => '歹', + '⽎' => '殳', + '⽏' => '毋', + '⽐' => '比', + '⽑' => '毛', + '⽒' => '氏', + '⽓' => '气', + '⽔' => '水', + '⽕' => '火', + '⽖' => '爪', + '⽗' => '父', + '⽘' => '爻', + '⽙' => '爿', + '⽚' => '片', + '⽛' => '牙', + '⽜' => '牛', + '⽝' => '犬', + '⽞' => '玄', + '⽟' => '玉', + '⽠' => '瓜', + '⽡' => '瓦', + '⽢' => '甘', + '⽣' => '生', + '⽤' => '用', + '⽥' => '田', + '⽦' => '疋', + '⽧' => '疒', + '⽨' => '癶', + '⽩' => '白', + '⽪' => '皮', + '⽫' => '皿', + '⽬' => '目', + '⽭' => '矛', + '⽮' => '矢', + '⽯' => '石', + '⽰' => '示', + '⽱' => '禸', + '⽲' => '禾', + '⽳' => '穴', + '⽴' => '立', + '⽵' => '竹', + '⽶' => '米', + '⽷' => '糸', + '⽸' => '缶', + '⽹' => '网', + '⽺' => '羊', + '⽻' => '羽', + '⽼' => '老', + '⽽' => '而', + '⽾' => '耒', + '⽿' => '耳', + '⾀' => '聿', + '⾁' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + '⾅' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => '虍', + '⾍' => '虫', + '⾎' => '血', + '⾏' => '行', + '⾐' => '衣', + '⾑' => '襾', + '⾒' => '見', + '⾓' => '角', + '⾔' => '言', + '⾕' => '谷', + '⾖' => '豆', + '⾗' => '豕', + '⾘' => '豸', + '⾙' => '貝', + '⾚' => '赤', + '⾛' => '走', + '⾜' => '足', + '⾝' => '身', + '⾞' => '車', + '⾟' => '辛', + '⾠' => '辰', + '⾡' => '辵', + '⾢' => '邑', + '⾣' => '酉', + '⾤' => '釆', + '⾥' => '里', + '⾦' => '金', + '⾧' => '長', + '⾨' => '門', + '⾩' => '阜', + '⾪' => '隶', + '⾫' => '隹', + '⾬' => '雨', + '⾭' => '靑', + '⾮' => '非', + '⾯' => '面', + '⾰' => '革', + '⾱' => '韋', + '⾲' => '韭', + '⾳' => '音', + '⾴' => '頁', + '⾵' => '風', + '⾶' => '飛', + '⾷' => '食', + '⾸' => '首', + '⾹' => '香', + '⾺' => '馬', + '⾻' => '骨', + '⾼' => '高', + '⾽' => '髟', + '⾾' => '鬥', + '⾿' => '鬯', + '⿀' => '鬲', + '⿁' => '鬼', + '⿂' => '魚', + '⿃' => '鳥', + '⿄' => '鹵', + '⿅' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => '黍', + '⿊' => '黑', + '⿋' => '黹', + '⿌' => '黽', + '⿍' => '鼎', + '⿎' => '鼓', + '⿏' => '鼠', + '⿐' => '鼻', + '⿑' => '齊', + '⿒' => '齒', + '⿓' => '龍', + '⿔' => '龜', + '⿕' => '龠', + ' ' => ' ', + '〶' => '〒', + '〸' => '十', + '〹' => '卄', + '〺' => '卅', + 'ㄱ' => 'ᄀ', + 'ㄲ' => 'ᄁ', + 'ㄳ' => 'ᆪ', + 'ㄴ' => 'ᄂ', + 'ㄵ' => 'ᆬ', + 'ㄶ' => 'ᆭ', + 'ㄷ' => 'ᄃ', + 'ㄸ' => 'ᄄ', + 'ㄹ' => 'ᄅ', + 'ㄺ' => 'ᆰ', + 'ㄻ' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ㄿ' => 'ᆵ', + 'ㅀ' => 'ᄚ', + 'ㅁ' => 'ᄆ', + 'ㅂ' => 'ᄇ', + 'ㅃ' => 'ᄈ', + 'ㅄ' => 'ᄡ', + 'ㅅ' => 'ᄉ', + 'ㅆ' => 'ᄊ', + 'ㅇ' => 'ᄋ', + 'ㅈ' => 'ᄌ', + 'ㅉ' => 'ᄍ', + 'ㅊ' => 'ᄎ', + 'ㅋ' => 'ᄏ', + 'ㅌ' => 'ᄐ', + 'ㅍ' => 'ᄑ', + 'ㅎ' => 'ᄒ', + 'ㅏ' => 'ᅡ', + 'ㅐ' => 'ᅢ', + 'ㅑ' => 'ᅣ', + 'ㅒ' => 'ᅤ', + 'ㅓ' => 'ᅥ', + 'ㅔ' => 'ᅦ', + 'ㅕ' => 'ᅧ', + 'ㅖ' => 'ᅨ', + 'ㅗ' => 'ᅩ', + 'ㅘ' => 'ᅪ', + 'ㅙ' => 'ᅫ', + 'ㅚ' => 'ᅬ', + 'ㅛ' => 'ᅭ', + 'ㅜ' => 'ᅮ', + 'ㅝ' => 'ᅯ', + 'ㅞ' => 'ᅰ', + 'ㅟ' => 'ᅱ', + 'ㅠ' => 'ᅲ', + 'ㅡ' => 'ᅳ', + 'ㅢ' => 'ᅴ', + 'ㅣ' => 'ᅵ', + 'ㅤ' => 'ᅠ', + 'ㅥ' => 'ᄔ', + 'ㅦ' => 'ᄕ', + 'ㅧ' => 'ᇇ', + 'ㅨ' => 'ᇈ', + 'ㅩ' => 'ᇌ', + 'ㅪ' => 'ᇎ', + 'ㅫ' => 'ᇓ', + 'ㅬ' => 'ᇗ', + 'ㅭ' => 'ᇙ', + 'ㅮ' => 'ᄜ', + 'ㅯ' => 'ᇝ', + 'ㅰ' => 'ᇟ', + 'ㅱ' => 'ᄝ', + 'ㅲ' => 'ᄞ', + 'ㅳ' => 'ᄠ', + 'ㅴ' => 'ᄢ', + 'ㅵ' => 'ᄣ', + 'ㅶ' => 'ᄧ', + 'ㅷ' => 'ᄩ', + 'ㅸ' => 'ᄫ', + 'ㅹ' => 'ᄬ', + 'ㅺ' => 'ᄭ', + 'ㅻ' => 'ᄮ', + 'ㅼ' => 'ᄯ', + 'ㅽ' => 'ᄲ', + 'ㅾ' => 'ᄶ', + 'ㅿ' => 'ᅀ', + 'ㆀ' => 'ᅇ', + 'ㆁ' => 'ᅌ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'ᅗ', + 'ㆅ' => 'ᅘ', + 'ㆆ' => 'ᅙ', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ㆍ' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㈀' => '(ᄀ)', + '㈁' => '(ᄂ)', + '㈂' => '(ᄃ)', + '㈃' => '(ᄅ)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(ᄋ)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(ᄏ)', + '㈋' => '(ᄐ)', + '㈌' => '(ᄑ)', + '㈍' => '(ᄒ)', + '㈎' => '(가)', + '㈏' => '(나)', + '㈐' => '(다)', + '㈑' => '(라)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(아)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(카)', + '㈙' => '(타)', + '㈚' => '(파)', + '㈛' => '(하)', + '㈜' => '(주)', + '㈝' => '(오전)', + '㈞' => '(오후)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(四)', + '㈤' => '(五)', + '㈥' => '(六)', + '㈦' => '(七)', + '㈧' => '(八)', + '㈨' => '(九)', + '㈩' => '(十)', + '㈪' => '(月)', + '㈫' => '(火)', + '㈬' => '(水)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(日)', + '㈱' => '(株)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(名)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(祝)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(学)', + '㈼' => '(監)', + '㈽' => '(企)', + '㈾' => '(資)', + '㈿' => '(協)', + '㉀' => '(祭)', + '㉁' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => '(問)', + '㉅' => '(幼)', + '㉆' => '(文)', + '㉇' => '(箏)', + '㉐' => 'PTE', + '㉑' => '(21)', + '㉒' => '(22)', + '㉓' => '(23)', + '㉔' => '(24)', + '㉕' => '(25)', + '㉖' => '(26)', + '㉗' => '(27)', + '㉘' => '(28)', + '㉙' => '(29)', + '㉚' => '(30)', + '㉛' => '(31)', + '㉜' => '(32)', + '㉝' => '(33)', + '㉞' => '(34)', + '㉟' => '(35)', + '㉠' => '(ᄀ)', + '㉡' => '(ᄂ)', + '㉢' => '(ᄃ)', + '㉣' => '(ᄅ)', + '㉤' => '(ᄆ)', + '㉥' => '(ᄇ)', + '㉦' => '(ᄉ)', + '㉧' => '(ᄋ)', + '㉨' => '(ᄌ)', + '㉩' => '(ᄎ)', + '㉪' => '(ᄏ)', + '㉫' => '(ᄐ)', + '㉬' => '(ᄑ)', + '㉭' => '(ᄒ)', + '㉮' => '(가)', + '㉯' => '(나)', + '㉰' => '(다)', + '㉱' => '(라)', + '㉲' => '(마)', + '㉳' => '(바)', + '㉴' => '(사)', + '㉵' => '(아)', + '㉶' => '(자)', + '㉷' => '(차)', + '㉸' => '(카)', + '㉹' => '(타)', + '㉺' => '(파)', + '㉻' => '(하)', + '㉼' => '(참고)', + '㉽' => '(주의)', + '㉾' => '(우)', + '㊀' => '(一)', + '㊁' => '(二)', + '㊂' => '(三)', + '㊃' => '(四)', + '㊄' => '(五)', + '㊅' => '(六)', + '㊆' => '(七)', + '㊇' => '(八)', + '㊈' => '(九)', + '㊉' => '(十)', + '㊊' => '(月)', + '㊋' => '(火)', + '㊌' => '(水)', + '㊍' => '(木)', + '㊎' => '(金)', + '㊏' => '(土)', + '㊐' => '(日)', + '㊑' => '(株)', + '㊒' => '(有)', + '㊓' => '(社)', + '㊔' => '(名)', + '㊕' => '(特)', + '㊖' => '(財)', + '㊗' => '(祝)', + '㊘' => '(労)', + '㊙' => '(秘)', + '㊚' => '(男)', + '㊛' => '(女)', + '㊜' => '(適)', + '㊝' => '(優)', + '㊞' => '(印)', + '㊟' => '(注)', + '㊠' => '(項)', + '㊡' => '(休)', + '㊢' => '(写)', + '㊣' => '(正)', + '㊤' => '(上)', + '㊥' => '(中)', + '㊦' => '(下)', + '㊧' => '(左)', + '㊨' => '(右)', + '㊩' => '(医)', + '㊪' => '(宗)', + '㊫' => '(学)', + '㊬' => '(監)', + '㊭' => '(企)', + '㊮' => '(資)', + '㊯' => '(協)', + '㊰' => '(夜)', + '㊱' => '(36)', + '㊲' => '(37)', + '㊳' => '(38)', + '㊴' => '(39)', + '㊵' => '(40)', + '㊶' => '(41)', + '㊷' => '(42)', + '㊸' => '(43)', + '㊹' => '(44)', + '㊺' => '(45)', + '㊻' => '(46)', + '㊼' => '(47)', + '㊽' => '(48)', + '㊾' => '(49)', + '㊿' => '(50)', + '㋀' => '1月', + '㋁' => '2月', + '㋂' => '3月', + '㋃' => '4月', + '㋄' => '5月', + '㋅' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + '㋋' => '12月', + '㋌' => 'Hg', + '㋍' => 'erg', + '㋎' => 'eV', + '㋏' => 'LTD', + '㋐' => '(ア)', + '㋑' => '(イ)', + '㋒' => '(ウ)', + '㋓' => '(エ)', + '㋔' => '(オ)', + '㋕' => '(カ)', + '㋖' => '(キ)', + '㋗' => '(ク)', + '㋘' => '(ケ)', + '㋙' => '(コ)', + '㋚' => '(サ)', + '㋛' => '(シ)', + '㋜' => '(ス)', + '㋝' => '(セ)', + '㋞' => '(ソ)', + '㋟' => '(タ)', + '㋠' => '(チ)', + '㋡' => '(ツ)', + '㋢' => '(テ)', + '㋣' => '(ト)', + '㋤' => '(ナ)', + '㋥' => '(ニ)', + '㋦' => '(ヌ)', + '㋧' => '(ネ)', + '㋨' => '(ノ)', + '㋩' => '(ハ)', + '㋪' => '(ヒ)', + '㋫' => '(フ)', + '㋬' => '(ヘ)', + '㋭' => '(ホ)', + '㋮' => '(マ)', + '㋯' => '(ミ)', + '㋰' => '(ム)', + '㋱' => '(メ)', + '㋲' => '(モ)', + '㋳' => '(ヤ)', + '㋴' => '(ユ)', + '㋵' => '(ヨ)', + '㋶' => '(ラ)', + '㋷' => '(リ)', + '㋸' => '(ル)', + '㋹' => '(レ)', + '㋺' => '(ロ)', + '㋻' => '(ワ)', + '㋼' => '(ヰ)', + '㋽' => '(ヱ)', + '㋾' => '(ヲ)', + '㋿' => '令和', + '㌀' => 'アパート', + '㌁' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インチ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + '㌍' => 'カロリー', + '㌎' => 'ガロン', + '㌏' => 'ガンマ', + '㌐' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローネ', + '㌜' => 'ケース', + '㌝' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンチーム', + '㌡' => 'シリング', + '㌢' => 'センチ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ハイツ', + '㌫' => 'パーセント', + '㌬' => 'パーツ', + '㌭' => 'バーレル', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + '㍀' => 'ポンド', + '㍁' => 'ホール', + '㍂' => 'ホーン', + '㍃' => 'マイクロ', + '㍄' => 'マイル', + '㍅' => 'マッハ', + '㍆' => 'マルク', + '㍇' => 'マンション', + '㍈' => 'ミクロン', + '㍉' => 'ミリ', + '㍊' => 'ミリバール', + '㍋' => 'メガ', + '㍌' => 'メガトン', + '㍍' => 'メートル', + '㍎' => 'ヤード', + '㍏' => 'ヤール', + '㍐' => 'ユアン', + '㍑' => 'リットル', + '㍒' => 'リラ', + '㍓' => 'ルピー', + '㍔' => 'ルーブル', + '㍕' => 'レム', + '㍖' => 'レントゲン', + '㍗' => 'ワット', + '㍘' => '0点', + '㍙' => '1点', + '㍚' => '2点', + '㍛' => '3点', + '㍜' => '4点', + '㍝' => '5点', + '㍞' => '6点', + '㍟' => '7点', + '㍠' => '8点', + '㍡' => '9点', + '㍢' => '10点', + '㍣' => '11点', + '㍤' => '12点', + '㍥' => '13点', + '㍦' => '14点', + '㍧' => '15点', + '㍨' => '16点', + '㍩' => '17点', + '㍪' => '18点', + '㍫' => '19点', + '㍬' => '20点', + '㍭' => '21点', + '㍮' => '22点', + '㍯' => '23点', + '㍰' => '24点', + '㍱' => 'hPa', + '㍲' => 'da', + '㍳' => 'AU', + '㍴' => 'bar', + '㍵' => 'oV', + '㍶' => 'pc', + '㍷' => 'dm', + '㍸' => 'dm²', + '㍹' => 'dm³', + '㍺' => 'IU', + '㍻' => '平成', + '㍼' => '昭和', + '㍽' => '大正', + '㍾' => '明治', + '㍿' => '株式会社', + '㎀' => 'pA', + '㎁' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + '㎍' => 'μg', + '㎎' => 'mg', + '㎏' => 'kg', + '㎐' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μℓ', + '㎖' => 'mℓ', + '㎗' => 'dℓ', + '㎘' => 'kℓ', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + '㎝' => 'cm', + '㎞' => 'km', + '㎟' => 'mm²', + '㎠' => 'cm²', + '㎡' => 'm²', + '㎢' => 'km²', + '㎣' => 'mm³', + '㎤' => 'cm³', + '㎥' => 'm³', + '㎦' => 'km³', + '㎧' => 'm∕s', + '㎨' => 'm∕s²', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s²', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + '㏀' => 'kΩ', + '㏁' => 'MΩ', + '㏂' => 'a.m.', + '㏃' => 'Bq', + '㏄' => 'cc', + '㏅' => 'cd', + '㏆' => 'C∕kg', + '㏇' => 'Co.', + '㏈' => 'dB', + '㏉' => 'Gy', + '㏊' => 'ha', + '㏋' => 'HP', + '㏌' => 'in', + '㏍' => 'KK', + '㏎' => 'KM', + '㏏' => 'kt', + '㏐' => 'lm', + '㏑' => 'ln', + '㏒' => 'log', + '㏓' => 'lx', + '㏔' => 'mb', + '㏕' => 'mil', + '㏖' => 'mol', + '㏗' => 'PH', + '㏘' => 'p.m.', + '㏙' => 'PPM', + '㏚' => 'PR', + '㏛' => 'sr', + '㏜' => 'Sv', + '㏝' => 'Wb', + '㏞' => 'V∕m', + '㏟' => 'A∕m', + '㏠' => '1日', + '㏡' => '2日', + '㏢' => '3日', + '㏣' => '4日', + '㏤' => '5日', + '㏥' => '6日', + '㏦' => '7日', + '㏧' => '8日', + '㏨' => '9日', + '㏩' => '10日', + '㏪' => '11日', + '㏫' => '12日', + '㏬' => '13日', + '㏭' => '14日', + '㏮' => '15日', + '㏯' => '16日', + '㏰' => '17日', + '㏱' => '18日', + '㏲' => '19日', + '㏳' => '20日', + '㏴' => '21日', + '㏵' => '22日', + '㏶' => '23日', + '㏷' => '24日', + '㏸' => '25日', + '㏹' => '26日', + '㏺' => '27日', + '㏻' => '28日', + '㏼' => '29日', + '㏽' => '30日', + '㏾' => '31日', + '㏿' => 'gal', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '﨎' => '' . "\0" . '', + '﨏' => '' . "\0" . '', + '塚' => '塚', + '﨑' => '' . "\0" . '', + '晴' => '晴', + '﨓' => '' . "\0" . '', + '﨔' => '' . "\0" . '', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '﨟' => '' . "\0" . '', + '蘒' => '蘒', + '﨡' => '' . "\0" . '', + '諸' => '諸', + '﨣' => '' . "\0" . '', + '﨤' => '' . "\0" . '', + '逸' => '逸', + '都' => '都', + '﨧' => '' . "\0" . '', + '﨨' => '' . "\0" . '', + '﨩' => '' . "\0" . '', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'ſt', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', + 'ﬠ' => 'ע', + 'ﬡ' => 'א', + 'ﬢ' => 'ד', + 'ﬣ' => 'ה', + 'ﬤ' => 'כ', + 'ﬥ' => 'ל', + 'ﬦ' => 'ם', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ﭏ' => 'אל', + '﹉' => '‾', + '﹊' => '‾', + '﹋' => '‾', + '﹌' => '‾', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => '、', + '﹒' => '.', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹘' => '—', + '﹙' => '(', + '﹚' => ')', + '﹛' => '{', + '﹜' => '}', + '﹝' => '〔', + '﹞' => '〕', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + '0' => '0', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + 'A' => 'A', + 'B' => 'B', + 'C' => 'C', + 'D' => 'D', + 'E' => 'E', + 'F' => 'F', + 'G' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'M' => 'M', + 'N' => 'N', + 'O' => 'O', + 'P' => 'P', + 'Q' => 'Q', + 'R' => 'R', + 'S' => 'S', + 'T' => 'T', + 'U' => 'U', + 'V' => 'V', + 'W' => 'W', + 'X' => 'X', + 'Y' => 'Y', + 'Z' => 'Z', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'e' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'm' => 'm', + 'n' => 'n', + 'o' => 'o', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'v' => 'v', + 'w' => 'w', + 'x' => 'x', + 'y' => 'y', + 'z' => 'z', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '⦅', + '⦆' => '⦆', + '。' => '。', + '「' => '「', + '」' => '」', + '、' => '、', + '・' => '・', + 'ヲ' => 'ヲ', + 'ァ' => 'ァ', + 'ィ' => 'ィ', + 'ゥ' => 'ゥ', + 'ェ' => 'ェ', + 'ォ' => 'ォ', + 'ャ' => 'ャ', + 'ュ' => 'ュ', + 'ョ' => 'ョ', + 'ッ' => 'ッ', + 'ー' => 'ー', + 'ア' => 'ア', + 'イ' => 'イ', + 'ウ' => 'ウ', + 'エ' => 'エ', + 'オ' => 'オ', + 'カ' => 'カ', + 'キ' => 'キ', + 'ク' => 'ク', + 'ケ' => 'ケ', + 'コ' => 'コ', + 'サ' => 'サ', + 'シ' => 'シ', + 'ス' => 'ス', + 'セ' => 'セ', + 'ソ' => 'ソ', + 'タ' => 'タ', + 'チ' => 'チ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ナ' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ネ', + 'ノ' => 'ノ', + 'ハ' => 'ハ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ヘ' => 'ヘ', + 'ホ' => 'ホ', + 'マ' => 'マ', + 'ミ' => 'ミ', + 'ム' => 'ム', + 'メ' => 'メ', + 'モ' => 'モ', + 'ヤ' => 'ヤ', + 'ユ' => 'ユ', + 'ヨ' => 'ヨ', + 'ラ' => 'ラ', + 'リ' => 'リ', + 'ル' => 'ル', + 'レ' => 'レ', + 'ロ' => 'ロ', + 'ワ' => 'ワ', + 'ン' => 'ン', + '゙' => '゙', + '゚' => '゚', + 'ᅠ' => 'ㅤ', + 'ᄀ' => 'ㄱ', + 'ᄁ' => 'ㄲ', + 'ᆪ' => 'ㄳ', + 'ᄂ' => 'ㄴ', + 'ᆬ' => 'ㄵ', + 'ᆭ' => 'ㄶ', + 'ᄃ' => 'ㄷ', + 'ᄄ' => 'ㄸ', + 'ᄅ' => 'ㄹ', + 'ᆰ' => 'ㄺ', + 'ᆱ' => 'ㄻ', + 'ᆲ' => 'ㄼ', + 'ᆳ' => 'ㄽ', + 'ᆴ' => 'ㄾ', + 'ᆵ' => 'ㄿ', + 'ᄚ' => 'ㅀ', + 'ᄆ' => 'ㅁ', + 'ᄇ' => 'ㅂ', + 'ᄈ' => 'ㅃ', + 'ᄡ' => 'ㅄ', + 'ᄉ' => 'ㅅ', + 'ᄊ' => 'ㅆ', + 'ᄋ' => 'ㅇ', + 'ᄌ' => 'ㅈ', + 'ᄍ' => 'ㅉ', + 'ᄎ' => 'ㅊ', + 'ᄏ' => 'ㅋ', + 'ᄐ' => 'ㅌ', + 'ᄑ' => 'ㅍ', + 'ᄒ' => 'ㅎ', + 'ᅡ' => 'ㅏ', + 'ᅢ' => 'ㅐ', + 'ᅣ' => 'ㅑ', + 'ᅤ' => 'ㅒ', + 'ᅥ' => 'ㅓ', + 'ᅦ' => 'ㅔ', + 'ᅧ' => 'ㅕ', + 'ᅨ' => 'ㅖ', + 'ᅩ' => 'ㅗ', + 'ᅪ' => 'ㅘ', + 'ᅫ' => 'ㅙ', + 'ᅬ' => 'ㅚ', + 'ᅭ' => 'ㅛ', + 'ᅮ' => 'ㅜ', + 'ᅯ' => 'ㅝ', + 'ᅰ' => 'ㅞ', + 'ᅱ' => 'ㅟ', + 'ᅲ' => 'ㅠ', + 'ᅳ' => 'ㅡ', + 'ᅴ' => 'ㅢ', + 'ᅵ' => 'ㅣ', + '¢' => '¢', + '£' => '£', + '¬' => '¬', + ' ̄' => '¯', + '¦' => '¦', + '¥' => '¥', + '₩' => '₩', + '│' => '│', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '■' => '■', + '○' => '○', + '𝐀' => 'A', + '𝐁' => 'B', + '𝐂' => 'C', + '𝐃' => 'D', + '𝐄' => 'E', + '𝐅' => 'F', + '𝐆' => 'G', + '𝐇' => 'H', + '𝐈' => 'I', + '𝐉' => 'J', + '𝐊' => 'K', + '𝐋' => 'L', + '𝐌' => 'M', + '𝐍' => 'N', + '𝐎' => 'O', + '𝐏' => 'P', + '𝐐' => 'Q', + '𝐑' => 'R', + '𝐒' => 'S', + '𝐓' => 'T', + '𝐔' => 'U', + '𝐕' => 'V', + '𝐖' => 'W', + '𝐗' => 'X', + '𝐘' => 'Y', + '𝐙' => 'Z', + '𝐚' => 'a', + '𝐛' => 'b', + '𝐜' => 'c', + '𝐝' => 'd', + '𝐞' => 'e', + '𝐟' => 'f', + '𝐠' => 'g', + '𝐡' => 'h', + '𝐢' => 'i', + '𝐣' => 'j', + '𝐤' => 'k', + '𝐥' => 'l', + '𝐦' => 'm', + '𝐧' => 'n', + '𝐨' => 'o', + '𝐩' => 'p', + '𝐪' => 'q', + '𝐫' => 'r', + '𝐬' => 's', + '𝐭' => 't', + '𝐮' => 'u', + '𝐯' => 'v', + '𝐰' => 'w', + '𝐱' => 'x', + '𝐲' => 'y', + '𝐳' => 'z', + '𝐴' => 'A', + '𝐵' => 'B', + '𝐶' => 'C', + '𝐷' => 'D', + '𝐸' => 'E', + '𝐹' => 'F', + '𝐺' => 'G', + '𝐻' => 'H', + '𝐼' => 'I', + '𝐽' => 'J', + '𝐾' => 'K', + '𝐿' => 'L', + '𝑀' => 'M', + '𝑁' => 'N', + '𝑂' => 'O', + '𝑃' => 'P', + '𝑄' => 'Q', + '𝑅' => 'R', + '𝑆' => 'S', + '𝑇' => 'T', + '𝑈' => 'U', + '𝑉' => 'V', + '𝑊' => 'W', + '𝑋' => 'X', + '𝑌' => 'Y', + '𝑍' => 'Z', + '𝑎' => 'a', + '𝑏' => 'b', + '𝑐' => 'c', + '𝑑' => 'd', + '𝑒' => 'e', + '𝑓' => 'f', + '𝑔' => 'g', + '𝑖' => 'i', + '𝑗' => 'j', + '𝑘' => 'k', + '𝑙' => 'l', + '𝑚' => 'm', + '𝑛' => 'n', + '𝑜' => 'o', + '𝑝' => 'p', + '𝑞' => 'q', + '𝑟' => 'r', + '𝑠' => 's', + '𝑡' => 't', + '𝑢' => 'u', + '𝑣' => 'v', + '𝑤' => 'w', + '𝑥' => 'x', + '𝑦' => 'y', + '𝑧' => 'z', + '𝑨' => 'A', + '𝑩' => 'B', + '𝑪' => 'C', + '𝑫' => 'D', + '𝑬' => 'E', + '𝑭' => 'F', + '𝑮' => 'G', + '𝑯' => 'H', + '𝑰' => 'I', + '𝑱' => 'J', + '𝑲' => 'K', + '𝑳' => 'L', + '𝑴' => 'M', + '𝑵' => 'N', + '𝑶' => 'O', + '𝑷' => 'P', + '𝑸' => 'Q', + '𝑹' => 'R', + '𝑺' => 'S', + '𝑻' => 'T', + '𝑼' => 'U', + '𝑽' => 'V', + '𝑾' => 'W', + '𝑿' => 'X', + '𝒀' => 'Y', + '𝒁' => 'Z', + '𝒂' => 'a', + '𝒃' => 'b', + '𝒄' => 'c', + '𝒅' => 'd', + '𝒆' => 'e', + '𝒇' => 'f', + '𝒈' => 'g', + '𝒉' => 'h', + '𝒊' => 'i', + '𝒋' => 'j', + '𝒌' => 'k', + '𝒍' => 'l', + '𝒎' => 'm', + '𝒏' => 'n', + '𝒐' => 'o', + '𝒑' => 'p', + '𝒒' => 'q', + '𝒓' => 'r', + '𝒔' => 's', + '𝒕' => 't', + '𝒖' => 'u', + '𝒗' => 'v', + '𝒘' => 'w', + '𝒙' => 'x', + '𝒚' => 'y', + '𝒛' => 'z', + '𝒜' => 'A', + '𝒞' => 'C', + '𝒟' => 'D', + '𝒢' => 'G', + '𝒥' => 'J', + '𝒦' => 'K', + '𝒩' => 'N', + '𝒪' => 'O', + '𝒫' => 'P', + '𝒬' => 'Q', + '𝒮' => 'S', + '𝒯' => 'T', + '𝒰' => 'U', + '𝒱' => 'V', + '𝒲' => 'W', + '𝒳' => 'X', + '𝒴' => 'Y', + '𝒵' => 'Z', + '𝒶' => 'a', + '𝒷' => 'b', + '𝒸' => 'c', + '𝒹' => 'd', + '𝒻' => 'f', + '𝒽' => 'h', + '𝒾' => 'i', + '𝒿' => 'j', + '𝓀' => 'k', + '𝓁' => 'l', + '𝓂' => 'm', + '𝓃' => 'n', + '𝓅' => 'p', + '𝓆' => 'q', + '𝓇' => 'r', + '𝓈' => 's', + '𝓉' => 't', + '𝓊' => 'u', + '𝓋' => 'v', + '𝓌' => 'w', + '𝓍' => 'x', + '𝓎' => 'y', + '𝓏' => 'z', + '𝓐' => 'A', + '𝓑' => 'B', + '𝓒' => 'C', + '𝓓' => 'D', + '𝓔' => 'E', + '𝓕' => 'F', + '𝓖' => 'G', + '𝓗' => 'H', + '𝓘' => 'I', + '𝓙' => 'J', + '𝓚' => 'K', + '𝓛' => 'L', + '𝓜' => 'M', + '𝓝' => 'N', + '𝓞' => 'O', + '𝓟' => 'P', + '𝓠' => 'Q', + '𝓡' => 'R', + '𝓢' => 'S', + '𝓣' => 'T', + '𝓤' => 'U', + '𝓥' => 'V', + '𝓦' => 'W', + '𝓧' => 'X', + '𝓨' => 'Y', + '𝓩' => 'Z', + '𝓪' => 'a', + '𝓫' => 'b', + '𝓬' => 'c', + '𝓭' => 'd', + '𝓮' => 'e', + '𝓯' => 'f', + '𝓰' => 'g', + '𝓱' => 'h', + '𝓲' => 'i', + '𝓳' => 'j', + '𝓴' => 'k', + '𝓵' => 'l', + '𝓶' => 'm', + '𝓷' => 'n', + '𝓸' => 'o', + '𝓹' => 'p', + '𝓺' => 'q', + '𝓻' => 'r', + '𝓼' => 's', + '𝓽' => 't', + '𝓾' => 'u', + '𝓿' => 'v', + '𝔀' => 'w', + '𝔁' => 'x', + '𝔂' => 'y', + '𝔃' => 'z', + '𝔄' => 'A', + '𝔅' => 'B', + '𝔇' => 'D', + '𝔈' => 'E', + '𝔉' => 'F', + '𝔊' => 'G', + '𝔍' => 'J', + '𝔎' => 'K', + '𝔏' => 'L', + '𝔐' => 'M', + '𝔑' => 'N', + '𝔒' => 'O', + '𝔓' => 'P', + '𝔔' => 'Q', + '𝔖' => 'S', + '𝔗' => 'T', + '𝔘' => 'U', + '𝔙' => 'V', + '𝔚' => 'W', + '𝔛' => 'X', + '𝔜' => 'Y', + '𝔞' => 'a', + '𝔟' => 'b', + '𝔠' => 'c', + '𝔡' => 'd', + '𝔢' => 'e', + '𝔣' => 'f', + '𝔤' => 'g', + '𝔥' => 'h', + '𝔦' => 'i', + '𝔧' => 'j', + '𝔨' => 'k', + '𝔩' => 'l', + '𝔪' => 'm', + '𝔫' => 'n', + '𝔬' => 'o', + '𝔭' => 'p', + '𝔮' => 'q', + '𝔯' => 'r', + '𝔰' => 's', + '𝔱' => 't', + '𝔲' => 'u', + '𝔳' => 'v', + '𝔴' => 'w', + '𝔵' => 'x', + '𝔶' => 'y', + '𝔷' => 'z', + '𝔸' => 'A', + '𝔹' => 'B', + '𝔻' => 'D', + '𝔼' => 'E', + '𝔽' => 'F', + '𝔾' => 'G', + '𝕀' => 'I', + '𝕁' => 'J', + '𝕂' => 'K', + '𝕃' => 'L', + '𝕄' => 'M', + '𝕆' => 'O', + '𝕊' => 'S', + '𝕋' => 'T', + '𝕌' => 'U', + '𝕍' => 'V', + '𝕎' => 'W', + '𝕏' => 'X', + '𝕐' => 'Y', + '𝕒' => 'a', + '𝕓' => 'b', + '𝕔' => 'c', + '𝕕' => 'd', + '𝕖' => 'e', + '𝕗' => 'f', + '𝕘' => 'g', + '𝕙' => 'h', + '𝕚' => 'i', + '𝕛' => 'j', + '𝕜' => 'k', + '𝕝' => 'l', + '𝕞' => 'm', + '𝕟' => 'n', + '𝕠' => 'o', + '𝕡' => 'p', + '𝕢' => 'q', + '𝕣' => 'r', + '𝕤' => 's', + '𝕥' => 't', + '𝕦' => 'u', + '𝕧' => 'v', + '𝕨' => 'w', + '𝕩' => 'x', + '𝕪' => 'y', + '𝕫' => 'z', + '𝕬' => 'A', + '𝕭' => 'B', + '𝕮' => 'C', + '𝕯' => 'D', + '𝕰' => 'E', + '𝕱' => 'F', + '𝕲' => 'G', + '𝕳' => 'H', + '𝕴' => 'I', + '𝕵' => 'J', + '𝕶' => 'K', + '𝕷' => 'L', + '𝕸' => 'M', + '𝕹' => 'N', + '𝕺' => 'O', + '𝕻' => 'P', + '𝕼' => 'Q', + '𝕽' => 'R', + '𝕾' => 'S', + '𝕿' => 'T', + '𝖀' => 'U', + '𝖁' => 'V', + '𝖂' => 'W', + '𝖃' => 'X', + '𝖄' => 'Y', + '𝖅' => 'Z', + '𝖆' => 'a', + '𝖇' => 'b', + '𝖈' => 'c', + '𝖉' => 'd', + '𝖊' => 'e', + '𝖋' => 'f', + '𝖌' => 'g', + '𝖍' => 'h', + '𝖎' => 'i', + '𝖏' => 'j', + '𝖐' => 'k', + '𝖑' => 'l', + '𝖒' => 'm', + '𝖓' => 'n', + '𝖔' => 'o', + '𝖕' => 'p', + '𝖖' => 'q', + '𝖗' => 'r', + '𝖘' => 's', + '𝖙' => 't', + '𝖚' => 'u', + '𝖛' => 'v', + '𝖜' => 'w', + '𝖝' => 'x', + '𝖞' => 'y', + '𝖟' => 'z', + '𝖠' => 'A', + '𝖡' => 'B', + '𝖢' => 'C', + '𝖣' => 'D', + '𝖤' => 'E', + '𝖥' => 'F', + '𝖦' => 'G', + '𝖧' => 'H', + '𝖨' => 'I', + '𝖩' => 'J', + '𝖪' => 'K', + '𝖫' => 'L', + '𝖬' => 'M', + '𝖭' => 'N', + '𝖮' => 'O', + '𝖯' => 'P', + '𝖰' => 'Q', + '𝖱' => 'R', + '𝖲' => 'S', + '𝖳' => 'T', + '𝖴' => 'U', + '𝖵' => 'V', + '𝖶' => 'W', + '𝖷' => 'X', + '𝖸' => 'Y', + '𝖹' => 'Z', + '𝖺' => 'a', + '𝖻' => 'b', + '𝖼' => 'c', + '𝖽' => 'd', + '𝖾' => 'e', + '𝖿' => 'f', + '𝗀' => 'g', + '𝗁' => 'h', + '𝗂' => 'i', + '𝗃' => 'j', + '𝗄' => 'k', + '𝗅' => 'l', + '𝗆' => 'm', + '𝗇' => 'n', + '𝗈' => 'o', + '𝗉' => 'p', + '𝗊' => 'q', + '𝗋' => 'r', + '𝗌' => 's', + '𝗍' => 't', + '𝗎' => 'u', + '𝗏' => 'v', + '𝗐' => 'w', + '𝗑' => 'x', + '𝗒' => 'y', + '𝗓' => 'z', + '𝗔' => 'A', + '𝗕' => 'B', + '𝗖' => 'C', + '𝗗' => 'D', + '𝗘' => 'E', + '𝗙' => 'F', + '𝗚' => 'G', + '𝗛' => 'H', + '𝗜' => 'I', + '𝗝' => 'J', + '𝗞' => 'K', + '𝗟' => 'L', + '𝗠' => 'M', + '𝗡' => 'N', + '𝗢' => 'O', + '𝗣' => 'P', + '𝗤' => 'Q', + '𝗥' => 'R', + '𝗦' => 'S', + '𝗧' => 'T', + '𝗨' => 'U', + '𝗩' => 'V', + '𝗪' => 'W', + '𝗫' => 'X', + '𝗬' => 'Y', + '𝗭' => 'Z', + '𝗮' => 'a', + '𝗯' => 'b', + '𝗰' => 'c', + '𝗱' => 'd', + '𝗲' => 'e', + '𝗳' => 'f', + '𝗴' => 'g', + '𝗵' => 'h', + '𝗶' => 'i', + '𝗷' => 'j', + '𝗸' => 'k', + '𝗹' => 'l', + '𝗺' => 'm', + '𝗻' => 'n', + '𝗼' => 'o', + '𝗽' => 'p', + '𝗾' => 'q', + '𝗿' => 'r', + '𝘀' => 's', + '𝘁' => 't', + '𝘂' => 'u', + '𝘃' => 'v', + '𝘄' => 'w', + '𝘅' => 'x', + '𝘆' => 'y', + '𝘇' => 'z', + '𝘈' => 'A', + '𝘉' => 'B', + '𝘊' => 'C', + '𝘋' => 'D', + '𝘌' => 'E', + '𝘍' => 'F', + '𝘎' => 'G', + '𝘏' => 'H', + '𝘐' => 'I', + '𝘑' => 'J', + '𝘒' => 'K', + '𝘓' => 'L', + '𝘔' => 'M', + '𝘕' => 'N', + '𝘖' => 'O', + '𝘗' => 'P', + '𝘘' => 'Q', + '𝘙' => 'R', + '𝘚' => 'S', + '𝘛' => 'T', + '𝘜' => 'U', + '𝘝' => 'V', + '𝘞' => 'W', + '𝘟' => 'X', + '𝘠' => 'Y', + '𝘡' => 'Z', + '𝘢' => 'a', + '𝘣' => 'b', + '𝘤' => 'c', + '𝘥' => 'd', + '𝘦' => 'e', + '𝘧' => 'f', + '𝘨' => 'g', + '𝘩' => 'h', + '𝘪' => 'i', + '𝘫' => 'j', + '𝘬' => 'k', + '𝘭' => 'l', + '𝘮' => 'm', + '𝘯' => 'n', + '𝘰' => 'o', + '𝘱' => 'p', + '𝘲' => 'q', + '𝘳' => 'r', + '𝘴' => 's', + '𝘵' => 't', + '𝘶' => 'u', + '𝘷' => 'v', + '𝘸' => 'w', + '𝘹' => 'x', + '𝘺' => 'y', + '𝘻' => 'z', + '𝘼' => 'A', + '𝘽' => 'B', + '𝘾' => 'C', + '𝘿' => 'D', + '𝙀' => 'E', + '𝙁' => 'F', + '𝙂' => 'G', + '𝙃' => 'H', + '𝙄' => 'I', + '𝙅' => 'J', + '𝙆' => 'K', + '𝙇' => 'L', + '𝙈' => 'M', + '𝙉' => 'N', + '𝙊' => 'O', + '𝙋' => 'P', + '𝙌' => 'Q', + '𝙍' => 'R', + '𝙎' => 'S', + '𝙏' => 'T', + '𝙐' => 'U', + '𝙑' => 'V', + '𝙒' => 'W', + '𝙓' => 'X', + '𝙔' => 'Y', + '𝙕' => 'Z', + '𝙖' => 'a', + '𝙗' => 'b', + '𝙘' => 'c', + '𝙙' => 'd', + '𝙚' => 'e', + '𝙛' => 'f', + '𝙜' => 'g', + '𝙝' => 'h', + '𝙞' => 'i', + '𝙟' => 'j', + '𝙠' => 'k', + '𝙡' => 'l', + '𝙢' => 'm', + '𝙣' => 'n', + '𝙤' => 'o', + '𝙥' => 'p', + '𝙦' => 'q', + '𝙧' => 'r', + '𝙨' => 's', + '𝙩' => 't', + '𝙪' => 'u', + '𝙫' => 'v', + '𝙬' => 'w', + '𝙭' => 'x', + '𝙮' => 'y', + '𝙯' => 'z', + '𝙰' => 'A', + '𝙱' => 'B', + '𝙲' => 'C', + '𝙳' => 'D', + '𝙴' => 'E', + '𝙵' => 'F', + '𝙶' => 'G', + '𝙷' => 'H', + '𝙸' => 'I', + '𝙹' => 'J', + '𝙺' => 'K', + '𝙻' => 'L', + '𝙼' => 'M', + '𝙽' => 'N', + '𝙾' => 'O', + '𝙿' => 'P', + '𝚀' => 'Q', + '𝚁' => 'R', + '𝚂' => 'S', + '𝚃' => 'T', + '𝚄' => 'U', + '𝚅' => 'V', + '𝚆' => 'W', + '𝚇' => 'X', + '𝚈' => 'Y', + '𝚉' => 'Z', + '𝚊' => 'a', + '𝚋' => 'b', + '𝚌' => 'c', + '𝚍' => 'd', + '𝚎' => 'e', + '𝚏' => 'f', + '𝚐' => 'g', + '𝚑' => 'h', + '𝚒' => 'i', + '𝚓' => 'j', + '𝚔' => 'k', + '𝚕' => 'l', + '𝚖' => 'm', + '𝚗' => 'n', + '𝚘' => 'o', + '𝚙' => 'p', + '𝚚' => 'q', + '𝚛' => 'r', + '𝚜' => 's', + '𝚝' => 't', + '𝚞' => 'u', + '𝚟' => 'v', + '𝚠' => 'w', + '𝚡' => 'x', + '𝚢' => 'y', + '𝚣' => 'z', + '𝚤' => 'ı', + '𝚥' => 'ȷ', + '𝚨' => 'Α', + '𝚩' => 'Β', + '𝚪' => 'Γ', + '𝚫' => 'Δ', + '𝚬' => 'Ε', + '𝚭' => 'Ζ', + '𝚮' => 'Η', + '𝚯' => 'Θ', + '𝚰' => 'Ι', + '𝚱' => 'Κ', + '𝚲' => 'Λ', + '𝚳' => 'Μ', + '𝚴' => 'Ν', + '𝚵' => 'Ξ', + '𝚶' => 'Ο', + '𝚷' => 'Π', + '𝚸' => 'Ρ', + '𝚹' => 'ϴ', + '𝚺' => 'Σ', + '𝚻' => 'Τ', + '𝚼' => 'Υ', + '𝚽' => 'Φ', + '𝚾' => 'Χ', + '𝚿' => 'Ψ', + '𝛀' => 'Ω', + '𝛁' => '∇', + '𝛂' => 'α', + '𝛃' => 'β', + '𝛄' => 'γ', + '𝛅' => 'δ', + '𝛆' => 'ε', + '𝛇' => 'ζ', + '𝛈' => 'η', + '𝛉' => 'θ', + '𝛊' => 'ι', + '𝛋' => 'κ', + '𝛌' => 'λ', + '𝛍' => 'μ', + '𝛎' => 'ν', + '𝛏' => 'ξ', + '𝛐' => 'ο', + '𝛑' => 'π', + '𝛒' => 'ρ', + '𝛓' => 'ς', + '𝛔' => 'σ', + '𝛕' => 'τ', + '𝛖' => 'υ', + '𝛗' => 'φ', + '𝛘' => 'χ', + '𝛙' => 'ψ', + '𝛚' => 'ω', + '𝛛' => '∂', + '𝛜' => 'ϵ', + '𝛝' => 'ϑ', + '𝛞' => 'ϰ', + '𝛟' => 'ϕ', + '𝛠' => 'ϱ', + '𝛡' => 'ϖ', + '𝛢' => 'Α', + '𝛣' => 'Β', + '𝛤' => 'Γ', + '𝛥' => 'Δ', + '𝛦' => 'Ε', + '𝛧' => 'Ζ', + '𝛨' => 'Η', + '𝛩' => 'Θ', + '𝛪' => 'Ι', + '𝛫' => 'Κ', + '𝛬' => 'Λ', + '𝛭' => 'Μ', + '𝛮' => 'Ν', + '𝛯' => 'Ξ', + '𝛰' => 'Ο', + '𝛱' => 'Π', + '𝛲' => 'Ρ', + '𝛳' => 'ϴ', + '𝛴' => 'Σ', + '𝛵' => 'Τ', + '𝛶' => 'Υ', + '𝛷' => 'Φ', + '𝛸' => 'Χ', + '𝛹' => 'Ψ', + '𝛺' => 'Ω', + '𝛻' => '∇', + '𝛼' => 'α', + '𝛽' => 'β', + '𝛾' => 'γ', + '𝛿' => 'δ', + '𝜀' => 'ε', + '𝜁' => 'ζ', + '𝜂' => 'η', + '𝜃' => 'θ', + '𝜄' => 'ι', + '𝜅' => 'κ', + '𝜆' => 'λ', + '𝜇' => 'μ', + '𝜈' => 'ν', + '𝜉' => 'ξ', + '𝜊' => 'ο', + '𝜋' => 'π', + '𝜌' => 'ρ', + '𝜍' => 'ς', + '𝜎' => 'σ', + '𝜏' => 'τ', + '𝜐' => 'υ', + '𝜑' => 'φ', + '𝜒' => 'χ', + '𝜓' => 'ψ', + '𝜔' => 'ω', + '𝜕' => '∂', + '𝜖' => 'ϵ', + '𝜗' => 'ϑ', + '𝜘' => 'ϰ', + '𝜙' => 'ϕ', + '𝜚' => 'ϱ', + '𝜛' => 'ϖ', + '𝜜' => 'Α', + '𝜝' => 'Β', + '𝜞' => 'Γ', + '𝜟' => 'Δ', + '𝜠' => 'Ε', + '𝜡' => 'Ζ', + '𝜢' => 'Η', + '𝜣' => 'Θ', + '𝜤' => 'Ι', + '𝜥' => 'Κ', + '𝜦' => 'Λ', + '𝜧' => 'Μ', + '𝜨' => 'Ν', + '𝜩' => 'Ξ', + '𝜪' => 'Ο', + '𝜫' => 'Π', + '𝜬' => 'Ρ', + '𝜭' => 'ϴ', + '𝜮' => 'Σ', + '𝜯' => 'Τ', + '𝜰' => 'Υ', + '𝜱' => 'Φ', + '𝜲' => 'Χ', + '𝜳' => 'Ψ', + '𝜴' => 'Ω', + '𝜵' => '∇', + '𝜶' => 'α', + '𝜷' => 'β', + '𝜸' => 'γ', + '𝜹' => 'δ', + '𝜺' => 'ε', + '𝜻' => 'ζ', + '𝜼' => 'η', + '𝜽' => 'θ', + '𝜾' => 'ι', + '𝜿' => 'κ', + '𝝀' => 'λ', + '𝝁' => 'μ', + '𝝂' => 'ν', + '𝝃' => 'ξ', + '𝝄' => 'ο', + '𝝅' => 'π', + '𝝆' => 'ρ', + '𝝇' => 'ς', + '𝝈' => 'σ', + '𝝉' => 'τ', + '𝝊' => 'υ', + '𝝋' => 'φ', + '𝝌' => 'χ', + '𝝍' => 'ψ', + '𝝎' => 'ω', + '𝝏' => '∂', + '𝝐' => 'ϵ', + '𝝑' => 'ϑ', + '𝝒' => 'ϰ', + '𝝓' => 'ϕ', + '𝝔' => 'ϱ', + '𝝕' => 'ϖ', + '𝝖' => 'Α', + '𝝗' => 'Β', + '𝝘' => 'Γ', + '𝝙' => 'Δ', + '𝝚' => 'Ε', + '𝝛' => 'Ζ', + '𝝜' => 'Η', + '𝝝' => 'Θ', + '𝝞' => 'Ι', + '𝝟' => 'Κ', + '𝝠' => 'Λ', + '𝝡' => 'Μ', + '𝝢' => 'Ν', + '𝝣' => 'Ξ', + '𝝤' => 'Ο', + '𝝥' => 'Π', + '𝝦' => 'Ρ', + '𝝧' => 'ϴ', + '𝝨' => 'Σ', + '𝝩' => 'Τ', + '𝝪' => 'Υ', + '𝝫' => 'Φ', + '𝝬' => 'Χ', + '𝝭' => 'Ψ', + '𝝮' => 'Ω', + '𝝯' => '∇', + '𝝰' => 'α', + '𝝱' => 'β', + '𝝲' => 'γ', + '𝝳' => 'δ', + '𝝴' => 'ε', + '𝝵' => 'ζ', + '𝝶' => 'η', + '𝝷' => 'θ', + '𝝸' => 'ι', + '𝝹' => 'κ', + '𝝺' => 'λ', + '𝝻' => 'μ', + '𝝼' => 'ν', + '𝝽' => 'ξ', + '𝝾' => 'ο', + '𝝿' => 'π', + '𝞀' => 'ρ', + '𝞁' => 'ς', + '𝞂' => 'σ', + '𝞃' => 'τ', + '𝞄' => 'υ', + '𝞅' => 'φ', + '𝞆' => 'χ', + '𝞇' => 'ψ', + '𝞈' => 'ω', + '𝞉' => '∂', + '𝞊' => 'ϵ', + '𝞋' => 'ϑ', + '𝞌' => 'ϰ', + '𝞍' => 'ϕ', + '𝞎' => 'ϱ', + '𝞏' => 'ϖ', + '𝞐' => 'Α', + '𝞑' => 'Β', + '𝞒' => 'Γ', + '𝞓' => 'Δ', + '𝞔' => 'Ε', + '𝞕' => 'Ζ', + '𝞖' => 'Η', + '𝞗' => 'Θ', + '𝞘' => 'Ι', + '𝞙' => 'Κ', + '𝞚' => 'Λ', + '𝞛' => 'Μ', + '𝞜' => 'Ν', + '𝞝' => 'Ξ', + '𝞞' => 'Ο', + '𝞟' => 'Π', + '𝞠' => 'Ρ', + '𝞡' => 'ϴ', + '𝞢' => 'Σ', + '𝞣' => 'Τ', + '𝞤' => 'Υ', + '𝞥' => 'Φ', + '𝞦' => 'Χ', + '𝞧' => 'Ψ', + '𝞨' => 'Ω', + '𝞩' => '∇', + '𝞪' => 'α', + '𝞫' => 'β', + '𝞬' => 'γ', + '𝞭' => 'δ', + '𝞮' => 'ε', + '𝞯' => 'ζ', + '𝞰' => 'η', + '𝞱' => 'θ', + '𝞲' => 'ι', + '𝞳' => 'κ', + '𝞴' => 'λ', + '𝞵' => 'μ', + '𝞶' => 'ν', + '𝞷' => 'ξ', + '𝞸' => 'ο', + '𝞹' => 'π', + '𝞺' => 'ρ', + '𝞻' => 'ς', + '𝞼' => 'σ', + '𝞽' => 'τ', + '𝞾' => 'υ', + '𝞿' => 'φ', + '𝟀' => 'χ', + '𝟁' => 'ψ', + '𝟂' => 'ω', + '𝟃' => '∂', + '𝟄' => 'ϵ', + '𝟅' => 'ϑ', + '𝟆' => 'ϰ', + '𝟇' => 'ϕ', + '𝟈' => 'ϱ', + '𝟉' => 'ϖ', + '𝟊' => 'Ϝ', + '𝟋' => 'ϝ', + '𝟎' => '0', + '𝟏' => '1', + '𝟐' => '2', + '𝟑' => '3', + '𝟒' => '4', + '𝟓' => '5', + '𝟔' => '6', + '𝟕' => '7', + '𝟖' => '8', + '𝟗' => '9', + '𝟘' => '0', + '𝟙' => '1', + '𝟚' => '2', + '𝟛' => '3', + '𝟜' => '4', + '𝟝' => '5', + '𝟞' => '6', + '𝟟' => '7', + '𝟠' => '8', + '𝟡' => '9', + '𝟢' => '0', + '𝟣' => '1', + '𝟤' => '2', + '𝟥' => '3', + '𝟦' => '4', + '𝟧' => '5', + '𝟨' => '6', + '𝟩' => '7', + '𝟪' => '8', + '𝟫' => '9', + '𝟬' => '0', + '𝟭' => '1', + '𝟮' => '2', + '𝟯' => '3', + '𝟰' => '4', + '𝟱' => '5', + '𝟲' => '6', + '𝟳' => '7', + '𝟴' => '8', + '𝟵' => '9', + '𝟶' => '0', + '𝟷' => '1', + '𝟸' => '2', + '𝟹' => '3', + '𝟺' => '4', + '𝟻' => '5', + '𝟼' => '6', + '𝟽' => '7', + '𝟾' => '8', + '𝟿' => '9', + '𞸀' => 'ا', + '𞸁' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'و', + '𞸆' => 'ز', + '𞸇' => 'ح', + '𞸈' => 'ط', + '𞸉' => 'ي', + '𞸊' => 'ك', + '𞸋' => 'ل', + '𞸌' => 'م', + '𞸍' => 'ن', + '𞸎' => 'س', + '𞸏' => 'ع', + '𞸐' => 'ف', + '𞸑' => 'ص', + '𞸒' => 'ق', + '𞸓' => 'ر', + '𞸔' => 'ش', + '𞸕' => 'ت', + '𞸖' => 'ث', + '𞸗' => 'خ', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'ٮ', + '𞸝' => 'ں', + '𞸞' => 'ڡ', + '𞸟' => 'ٯ', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'ه', + '𞸧' => 'ح', + '𞸩' => 'ي', + '𞸪' => 'ك', + '𞸫' => 'ل', + '𞸬' => 'م', + '𞸭' => 'ن', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'ف', + '𞸱' => 'ص', + '𞸲' => 'ق', + '𞸴' => 'ش', + '𞸵' => 'ت', + '𞸶' => 'ث', + '𞸷' => 'خ', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'ح', + '𞹉' => 'ي', + '𞹋' => 'ل', + '𞹍' => 'ن', + '𞹎' => 'س', + '𞹏' => 'ع', + '𞹑' => 'ص', + '𞹒' => 'ق', + '𞹔' => 'ش', + '𞹗' => 'خ', + '𞹙' => 'ض', + '𞹛' => 'غ', + '𞹝' => 'ں', + '𞹟' => 'ٯ', + '𞹡' => 'ب', + '𞹢' => 'ج', + '𞹤' => 'ه', + '𞹧' => 'ح', + '𞹨' => 'ط', + '𞹩' => 'ي', + '𞹪' => 'ك', + '𞹬' => 'م', + '𞹭' => 'ن', + '𞹮' => 'س', + '𞹯' => 'ع', + '𞹰' => 'ف', + '𞹱' => 'ص', + '𞹲' => 'ق', + '𞹴' => 'ش', + '𞹵' => 'ت', + '𞹶' => 'ث', + '𞹷' => 'خ', + '𞹹' => 'ض', + '𞹺' => 'ظ', + '𞹻' => 'غ', + '𞹼' => 'ٮ', + '𞹾' => 'ڡ', + '𞺀' => 'ا', + '𞺁' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'ه', + '𞺅' => 'و', + '𞺆' => 'ز', + '𞺇' => 'ح', + '𞺈' => 'ط', + '𞺉' => 'ي', + '𞺋' => 'ل', + '𞺌' => 'م', + '𞺍' => 'ن', + '𞺎' => 'س', + '𞺏' => 'ع', + '𞺐' => 'ف', + '𞺑' => 'ص', + '𞺒' => 'ق', + '𞺓' => 'ر', + '𞺔' => 'ش', + '𞺕' => 'ت', + '𞺖' => 'ث', + '𞺗' => 'خ', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'و', + '𞺦' => 'ز', + '𞺧' => 'ح', + '𞺨' => 'ط', + '𞺩' => 'ي', + '𞺫' => 'ل', + '𞺬' => 'م', + '𞺭' => 'ن', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'ف', + '𞺱' => 'ص', + '𞺲' => 'ق', + '𞺳' => 'ر', + '𞺴' => 'ش', + '𞺵' => 'ت', + '𞺶' => 'ث', + '𞺷' => 'خ', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + '🄁' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + '🄐' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + '🄝' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => '(C)', + '🄬' => '(R)', + '🄭' => '(CD)', + '🄮' => '(WZ)', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + '🅁' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + '🅍' => 'SS', + '🅎' => 'PPV', + '🅏' => 'WC', + '🆐' => 'DJ', + '🈀' => 'ほか', + '🈁' => 'ココ', + '🈂' => 'サ', + '🈐' => '手', + '🈑' => '字', + '🈒' => '双', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => '解', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => '無', + '🈛' => '料', + '🈜' => '前', + '🈝' => '後', + '🈞' => '再', + '🈟' => '新', + '🈠' => '初', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => '吹', + '🈦' => '演', + '🈧' => '投', + '🈨' => '捕', + '🈩' => '一', + '🈪' => '三', + '🈫' => '遊', + '🈬' => '左', + '🈭' => '中', + '🈮' => '右', + '🈯' => '指', + '🈰' => '走', + '🈱' => '打', + '🈲' => '禁', + '🈳' => '空', + '🈴' => '合', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => '営', + '🈻' => '配', + '🉀' => '〔本〕', + '🉁' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔勝〕', + '🉈' => '〔敗〕', + '🉐' => '(得)', + '🉑' => '(可)', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', + 'Æ' => 'AE', + 'Ð' => 'D', + 'Ø' => 'O', + 'Þ' => 'TH', + 'ß' => 'ss', + 'æ' => 'ae', + 'ð' => 'd', + 'ø' => 'o', + 'þ' => 'th', + 'Đ' => 'D', + 'đ' => 'd', + 'Ħ' => 'H', + 'ħ' => 'h', + 'ı' => 'i', + 'ĸ' => 'q', + 'Ł' => 'L', + 'ł' => 'l', + 'Ŋ' => 'N', + 'ŋ' => 'n', + 'Œ' => 'OE', + 'œ' => 'oe', + 'Ŧ' => 'T', + 'ŧ' => 't', + 'ƀ' => 'b', + 'Ɓ' => 'B', + 'Ƃ' => 'B', + 'ƃ' => 'b', + 'Ƈ' => 'C', + 'ƈ' => 'c', + 'Ɖ' => 'D', + 'Ɗ' => 'D', + 'Ƌ' => 'D', + 'ƌ' => 'd', + 'Ɛ' => 'E', + 'Ƒ' => 'F', + 'ƒ' => 'f', + 'Ɠ' => 'G', + 'ƕ' => 'hv', + 'Ɩ' => 'I', + 'Ɨ' => 'I', + 'Ƙ' => 'K', + 'ƙ' => 'k', + 'ƚ' => 'l', + 'Ɲ' => 'N', + 'ƞ' => 'n', + 'Ƣ' => 'OI', + 'ƣ' => 'oi', + 'Ƥ' => 'P', + 'ƥ' => 'p', + 'ƫ' => 't', + 'Ƭ' => 'T', + 'ƭ' => 't', + 'Ʈ' => 'T', + 'Ʋ' => 'V', + 'Ƴ' => 'Y', + 'ƴ' => 'y', + 'Ƶ' => 'Z', + 'ƶ' => 'z', + 'Ǥ' => 'G', + 'ǥ' => 'g', + 'ȡ' => 'd', + 'Ȥ' => 'Z', + 'ȥ' => 'z', + 'ȴ' => 'l', + 'ȵ' => 'n', + 'ȶ' => 't', + 'ȷ' => 'j', + 'ȸ' => 'db', + 'ȹ' => 'qp', + 'Ⱥ' => 'A', + 'Ȼ' => 'C', + 'ȼ' => 'c', + 'Ƚ' => 'L', + 'Ⱦ' => 'T', + 'ȿ' => 's', + 'ɀ' => 'z', + 'Ƀ' => 'B', + 'Ʉ' => 'U', + 'Ɇ' => 'E', + 'ɇ' => 'e', + 'Ɉ' => 'J', + 'ɉ' => 'j', + 'Ɍ' => 'R', + 'ɍ' => 'r', + 'Ɏ' => 'Y', + 'ɏ' => 'y', + 'ɓ' => 'b', + 'ɕ' => 'c', + 'ɖ' => 'd', + 'ɗ' => 'd', + 'ɛ' => 'e', + 'ɟ' => 'j', + 'ɠ' => 'g', + 'ɡ' => 'g', + 'ɢ' => 'G', + 'ɦ' => 'h', + 'ɧ' => 'h', + 'ɨ' => 'i', + 'ɪ' => 'I', + 'ɫ' => 'l', + 'ɬ' => 'l', + 'ɭ' => 'l', + 'ɱ' => 'm', + 'ɲ' => 'n', + 'ɳ' => 'n', + 'ɴ' => 'N', + 'ɶ' => 'OE', + 'ɼ' => 'r', + 'ɽ' => 'r', + 'ɾ' => 'r', + 'ʀ' => 'R', + 'ʂ' => 's', + 'ʈ' => 't', + 'ʉ' => 'u', + 'ʋ' => 'v', + 'ʏ' => 'Y', + 'ʐ' => 'z', + 'ʑ' => 'z', + 'ʙ' => 'B', + 'ʛ' => 'G', + 'ʜ' => 'H', + 'ʝ' => 'j', + 'ʟ' => 'L', + 'ʠ' => 'q', + 'ʣ' => 'dz', + 'ʥ' => 'dz', + 'ʦ' => 'ts', + 'ʪ' => 'ls', + 'ʫ' => 'lz', + 'ᴀ' => 'A', + 'ᴁ' => 'AE', + 'ᴃ' => 'B', + 'ᴄ' => 'C', + 'ᴅ' => 'D', + 'ᴆ' => 'D', + 'ᴇ' => 'E', + 'ᴊ' => 'J', + 'ᴋ' => 'K', + 'ᴌ' => 'L', + 'ᴍ' => 'M', + 'ᴏ' => 'O', + 'ᴘ' => 'P', + 'ᴛ' => 'T', + 'ᴜ' => 'U', + 'ᴠ' => 'V', + 'ᴡ' => 'W', + 'ᴢ' => 'Z', + 'ᵫ' => 'ue', + 'ᵬ' => 'b', + 'ᵭ' => 'd', + 'ᵮ' => 'f', + 'ᵯ' => 'm', + 'ᵰ' => 'n', + 'ᵱ' => 'p', + 'ᵲ' => 'r', + 'ᵳ' => 'r', + 'ᵴ' => 's', + 'ᵵ' => 't', + 'ᵶ' => 'z', + 'ᵺ' => 'th', + 'ᵻ' => 'I', + 'ᵽ' => 'p', + 'ᵾ' => 'U', + 'ᶀ' => 'b', + 'ᶁ' => 'd', + 'ᶂ' => 'f', + 'ᶃ' => 'g', + 'ᶄ' => 'k', + 'ᶅ' => 'l', + 'ᶆ' => 'm', + 'ᶇ' => 'n', + 'ᶈ' => 'p', + 'ᶉ' => 'r', + 'ᶊ' => 's', + 'ᶌ' => 'v', + 'ᶍ' => 'x', + 'ᶎ' => 'z', + 'ᶏ' => 'a', + 'ᶑ' => 'd', + 'ᶒ' => 'e', + 'ᶓ' => 'e', + 'ᶖ' => 'i', + 'ᶙ' => 'u', + 'ẜ' => 's', + 'ẝ' => 's', + 'ẞ' => 'SS', + 'Ỻ' => 'LL', + 'ỻ' => 'll', + 'Ỽ' => 'V', + 'ỽ' => 'v', + 'Ỿ' => 'Y', + 'ỿ' => 'y', + 'Ⱡ' => 'L', + 'ⱡ' => 'l', + 'Ɫ' => 'L', + 'Ᵽ' => 'P', + 'Ɽ' => 'R', + 'ⱥ' => 'a', + 'ⱦ' => 't', + 'Ⱨ' => 'H', + 'ⱨ' => 'h', + 'Ⱪ' => 'K', + 'ⱪ' => 'k', + 'Ⱬ' => 'Z', + 'ⱬ' => 'z', + 'Ɱ' => 'M', + 'ⱱ' => 'v', + 'Ⱳ' => 'W', + 'ⱳ' => 'w', + 'ⱴ' => 'v', + 'ⱸ' => 'e', + 'ⱺ' => 'o', + 'Ȿ' => 'S', + 'Ɀ' => 'Z', + 'ꜰ' => 'F', + 'ꜱ' => 'S', + 'Ꜳ' => 'AA', + 'ꜳ' => 'aa', + 'Ꜵ' => 'AO', + 'ꜵ' => 'ao', + 'Ꜷ' => 'AU', + 'ꜷ' => 'au', + 'Ꜹ' => 'AV', + 'ꜹ' => 'av', + 'Ꜻ' => 'AV', + 'ꜻ' => 'av', + 'Ꜽ' => 'AY', + 'ꜽ' => 'ay', + 'Ꝁ' => 'K', + 'ꝁ' => 'k', + 'Ꝃ' => 'K', + 'ꝃ' => 'k', + 'Ꝅ' => 'K', + 'ꝅ' => 'k', + 'Ꝇ' => 'L', + 'ꝇ' => 'l', + 'Ꝉ' => 'L', + 'ꝉ' => 'l', + 'Ꝋ' => 'O', + 'ꝋ' => 'o', + 'Ꝍ' => 'O', + 'ꝍ' => 'o', + 'Ꝏ' => 'OO', + 'ꝏ' => 'oo', + 'Ꝑ' => 'P', + 'ꝑ' => 'p', + 'Ꝓ' => 'P', + 'ꝓ' => 'p', + 'Ꝕ' => 'P', + 'ꝕ' => 'p', + 'Ꝗ' => 'Q', + 'ꝗ' => 'q', + 'Ꝙ' => 'Q', + 'ꝙ' => 'q', + 'Ꝟ' => 'V', + 'ꝟ' => 'v', + 'Ꝡ' => 'VY', + 'ꝡ' => 'vy', + 'Ꝥ' => 'TH', + 'ꝥ' => 'th', + 'Ꝧ' => 'TH', + 'ꝧ' => 'th', + 'ꝱ' => 'd', + 'ꝲ' => 'l', + 'ꝳ' => 'm', + 'ꝴ' => 'n', + 'ꝵ' => 'r', + 'ꝶ' => 'R', + 'ꝷ' => 't', + 'Ꝺ' => 'D', + 'ꝺ' => 'd', + 'Ꝼ' => 'F', + 'ꝼ' => 'f', + 'Ꞇ' => 'T', + 'ꞇ' => 't', + 'Ꞑ' => 'N', + 'ꞑ' => 'n', + 'Ꞓ' => 'C', + 'ꞓ' => 'c', + 'Ꞡ' => 'G', + 'ꞡ' => 'g', + 'Ꞣ' => 'K', + 'ꞣ' => 'k', + 'Ꞥ' => 'N', + 'ꞥ' => 'n', + 'Ꞧ' => 'R', + 'ꞧ' => 'r', + 'Ꞩ' => 'S', + 'ꞩ' => 's', + 'Ɦ' => 'H', + '©' => '(C)', + '®' => '(R)', + '₠' => 'CE', + '₢' => 'Cr', + '₣' => 'Fr.', + '₤' => 'L.', + '₧' => 'Pts', + '₺' => 'TL', + '₹' => 'Rs', + '℗' => '(P)', + '℘' => 'P', + '℞' => 'Rx', + '〇' => '0', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + 'ʹ' => '\'', + 'ʺ' => '"', + 'ʻ' => '\'', + 'ʼ' => '\'', + 'ʽ' => '\'', + 'ˈ' => '\'', + 'ˋ' => '`', + '‘' => '\'', + '’' => '\'', + '‚' => ',', + '‛' => '\'', + '“' => '"', + '”' => '"', + '„' => ',,', + '‟' => '"', + '′' => '\'', + '〝' => '"', + '〞' => '"', + '«' => '<<', + '»' => '>>', + '‹' => '<', + '›' => '>', + '­' => '-', + '‐' => '-', + '‑' => '-', + '‒' => '-', + '–' => '-', + '—' => '-', + '―' => '-', + '︱' => '-', + '︲' => '-', + '˂' => '<', + '˃' => '>', + '˄' => '^', + 'ˆ' => '^', + 'ː' => ':', + '˜' => '~', + '‖' => '||', + '⁄' => '/', + '⁅' => '[', + '⁆' => ']', + '⁎' => '*', + '、' => ',', + '。' => '.', + '〈' => '<', + '〉' => '>', + '《' => '<<', + '》' => '>>', + '〔' => '[', + '〕' => ']', + '〘' => '[', + '〙' => ']', + '〚' => '[', + '〛' => ']', + '︐' => ',', + '︑' => ',', + '︒' => '.', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︙' => '...', + '︰' => '..', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '[', + '︺' => ']', + '︽' => '<<', + '︾' => '>>', + '︿' => '<', + '﹀' => '>', + '﹇' => '[', + '﹈' => ']', + '×' => '*', + '÷' => '/', + '˖' => '+', + '˗' => '-', + '−' => '-', + '∕' => '/', + '∖' => '\\', + '∣' => '|', + '∥' => '||', + '≪' => '<<', + '≫' => '>>', + '⦅' => '((', + '⦆' => '))', +); diff --git a/plugins/email/vendor/symfony/polyfill-iconv/bootstrap.php b/plugins/email/vendor/symfony/polyfill-iconv/bootstrap.php new file mode 100644 index 0000000..91fdba0 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/bootstrap.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Iconv as p; + +if (extension_loaded('iconv')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('ICONV_IMPL')) { + define('ICONV_IMPL', 'Symfony'); +} +if (!defined('ICONV_VERSION')) { + define('ICONV_VERSION', '1.0'); +} +if (!defined('ICONV_MIME_DECODE_STRICT')) { + define('ICONV_MIME_DECODE_STRICT', 1); +} +if (!defined('ICONV_MIME_DECODE_CONTINUE_ON_ERROR')) { + define('ICONV_MIME_DECODE_CONTINUE_ON_ERROR', 2); +} + +if (!function_exists('iconv')) { + function iconv($from_encoding, $to_encoding, $string) { return p\Iconv::iconv($from_encoding, $to_encoding, $string); } +} +if (!function_exists('iconv_get_encoding')) { + function iconv_get_encoding($type = 'all') { return p\Iconv::iconv_get_encoding($type); } +} +if (!function_exists('iconv_set_encoding')) { + function iconv_set_encoding($type, $encoding) { return p\Iconv::iconv_set_encoding($type, $encoding); } +} +if (!function_exists('iconv_mime_encode')) { + function iconv_mime_encode($field_name, $field_value, $options = []) { return p\Iconv::iconv_mime_encode($field_name, $field_value, $options); } +} +if (!function_exists('iconv_mime_decode_headers')) { + function iconv_mime_decode_headers($headers, $mode = 0, $encoding = null) { return p\Iconv::iconv_mime_decode_headers($headers, $mode, $encoding); } +} + +if (extension_loaded('mbstring')) { + if (!function_exists('iconv_strlen')) { + function iconv_strlen($string, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strlen($string, $encoding); } + } + if (!function_exists('iconv_strpos')) { + function iconv_strpos($haystack, $needle, $offset = 0, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strpos($haystack, $needle, $offset, $encoding); } + } + if (!function_exists('iconv_strrpos')) { + function iconv_strrpos($haystack, $needle, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strrpos($haystack, $needle, 0, $encoding); } + } + if (!function_exists('iconv_substr')) { + function iconv_substr($string, $offset, $length = 2147483647, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_substr($string, $offset, $length, $encoding); } + } + if (!function_exists('iconv_mime_decode')) { + function iconv_mime_decode($string, $mode = 0, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_decode_mimeheader($string, $mode, $encoding); } + } +} else { + if (!function_exists('iconv_strlen')) { + if (extension_loaded('xml')) { + function iconv_strlen($string, $encoding = null) { return p\Iconv::strlen1($string, $encoding); } + } else { + function iconv_strlen($string, $encoding = null) { return p\Iconv::strlen2($string, $encoding); } + } + } + + if (!function_exists('iconv_strpos')) { + function iconv_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Iconv::iconv_strpos($haystack, $needle, $offset, $encoding); } + } + if (!function_exists('iconv_strrpos')) { + function iconv_strrpos($haystack, $needle, $encoding = null) { return p\Iconv::iconv_strrpos($haystack, $needle, $encoding); } + } + if (!function_exists('iconv_substr')) { + function iconv_substr($string, $offset, $length = 2147483647, $encoding = null) { return p\Iconv::iconv_substr($string, $offset, $length, $encoding); } + } + if (!function_exists('iconv_mime_decode')) { + function iconv_mime_decode($string, $mode = 0, $encoding = null) { return p\Iconv::iconv_mime_decode($string, $mode, $encoding); } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-iconv/bootstrap80.php b/plugins/email/vendor/symfony/polyfill-iconv/bootstrap80.php new file mode 100644 index 0000000..2dd8bf7 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/bootstrap80.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Iconv as p; + +if (!defined('ICONV_IMPL')) { + define('ICONV_IMPL', 'Symfony'); +} +if (!defined('ICONV_VERSION')) { + define('ICONV_VERSION', '1.0'); +} +if (!defined('ICONV_MIME_DECODE_STRICT')) { + define('ICONV_MIME_DECODE_STRICT', 1); +} +if (!defined('ICONV_MIME_DECODE_CONTINUE_ON_ERROR')) { + define('ICONV_MIME_DECODE_CONTINUE_ON_ERROR', 2); +} + +if (!function_exists('iconv')) { + function iconv(string $from_encoding, string $to_encoding, string $string): string|false { return p\Iconv::iconv($from_encoding, $to_encoding, $string); } +} +if (!function_exists('iconv_get_encoding')) { + function iconv_get_encoding(string $type = 'all'): array|string|false { return p\Iconv::iconv_get_encoding($type); } +} +if (!function_exists('iconv_set_encoding')) { + function iconv_set_encoding(string $type, string $encoding): bool { return p\Iconv::iconv_set_encoding($type, $encoding); } +} +if (!function_exists('iconv_mime_encode')) { + function iconv_mime_encode(string $field_name, string $field_value, array $options = []): string|false { return p\Iconv::iconv_mime_encode($field_name, $field_value, $options); } +} +if (!function_exists('iconv_mime_decode_headers')) { + function iconv_mime_decode_headers(string $headers, int $mode = 0, string $encoding = null): array|false { return p\Iconv::iconv_mime_decode_headers($headers, $mode, $encoding); } +} + +if (extension_loaded('mbstring')) { + if (!function_exists('iconv_strlen')) { + function iconv_strlen(string $string, string $encoding = null): int|false { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strlen($string, $encoding); } + } + if (!function_exists('iconv_strpos')) { + function iconv_strpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strpos($haystack, $needle, $offset, $encoding); } + } + if (!function_exists('iconv_strrpos')) { + function iconv_strrpos(string $haystack, string $needle, string $encoding = null): int|false { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_strrpos($haystack, $needle, 0, $encoding); } + } + if (!function_exists('iconv_substr')) { + function iconv_substr(string $string, int $offset, int $length = null, string $encoding = null): string|false { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_substr($string, $offset, $length, $encoding); } + } + if (!function_exists('iconv_mime_decode')) { + function iconv_mime_decode($string, $mode = 0, $encoding = null) { null === $encoding && $encoding = p\Iconv::$internalEncoding; return mb_decode_mimeheader($string, $mode, $encoding); } + } +} else { + if (!function_exists('iconv_strlen')) { + if (extension_loaded('xml')) { + function iconv_strlen(string $string, string $encoding = null): int|false { return p\Iconv::strlen1($string, $encoding); } + } else { + function iconv_strlen(string $string, string $encoding = null): int|false { return p\Iconv::strlen2($string, $encoding); } + } + } + + if (!function_exists('iconv_strpos')) { + function iconv_strpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { return p\Iconv::iconv_strpos($haystack, $needle, $offset, $encoding); } + } + if (!function_exists('iconv_strrpos')) { + function iconv_strrpos(string $haystack, string $needle, string $encoding = null): int|false { return p\Iconv::iconv_strrpos($haystack, $needle, $encoding); } + } + if (!function_exists('iconv_substr')) { + function iconv_substr(string $string, int $offset, int $length = null, string $encoding = null): string|false { return p\Iconv::iconv_substr($string, $offset, $length, $encoding); } + } + if (!function_exists('iconv_mime_decode')) { + function iconv_mime_decode(string $string, int $mode = 0, string $encoding = null): string|false { return p\Iconv::iconv_mime_decode($string, $mode, $encoding); } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-iconv/composer.json b/plugins/email/vendor/symfony/polyfill-iconv/composer.json new file mode 100644 index 0000000..4669f3f --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-iconv/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-iconv", + "type": "library", + "description": "Symfony polyfill for the Iconv extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "iconv"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Iconv\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Idn.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Idn.php new file mode 100644 index 0000000..fee3026 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Idn.php @@ -0,0 +1,925 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +use Exception; +use Normalizer; +use Symfony\Polyfill\Intl\Idn\Resources\unidata\DisallowedRanges; +use Symfony\Polyfill\Intl\Idn\Resources\unidata\Regex; + +/** + * @see https://www.unicode.org/reports/tr46/ + * + * @internal + */ +final class Idn +{ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + + public const INTL_IDNA_VARIANT_2003 = 0; + public const INTL_IDNA_VARIANT_UTS46 = 1; + + public const IDNA_DEFAULT = 0; + public const IDNA_ALLOW_UNASSIGNED = 1; + public const IDNA_USE_STD3_RULES = 2; + public const IDNA_CHECK_BIDI = 4; + public const IDNA_CHECK_CONTEXTJ = 8; + public const IDNA_NONTRANSITIONAL_TO_ASCII = 16; + public const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; + + public const MAX_DOMAIN_SIZE = 253; + public const MAX_LABEL_SIZE = 63; + + public const BASE = 36; + public const TMIN = 1; + public const TMAX = 26; + public const SKEW = 38; + public const DAMP = 700; + public const INITIAL_BIAS = 72; + public const INITIAL_N = 128; + public const DELIMITER = '-'; + public const MAX_INT = 2147483647; + + /** + * Contains the numeric value of a basic code point (for use in representing integers) in the + * range 0 to BASE-1, or -1 if b is does not represent a value. + * + * @var array + */ + private static $basicToDigit = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + ]; + + /** + * @var array + */ + private static $virama; + + /** + * @var array + */ + private static $mapped; + + /** + * @var array + */ + private static $ignored; + + /** + * @var array + */ + private static $deviation; + + /** + * @var array + */ + private static $disallowed; + + /** + * @var array + */ + private static $disallowed_STD3_mapped; + + /** + * @var array + */ + private static $disallowed_STD3_valid; + + /** + * @var bool + */ + private static $mappingTableLoaded = false; + + /** + * @see https://www.unicode.org/reports/tr46/#ToASCII + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $options = [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), + 'VerifyDnsLength' => true, + ]; + $info = new Info(); + $labels = self::process((string) $domainName, $options, $info); + + foreach ($labels as $i => $label) { + // Only convert labels to punycode that contain non-ASCII code points + if (1 === preg_match('/[^\x00-\x7F]/', $label)) { + try { + $label = 'xn--'.self::punycodeEncode($label); + } catch (Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + } + + $labels[$i] = $label; + } + } + + if ($options['VerifyDnsLength']) { + self::validateDomainAndLabelLength($labels, $info); + } + + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ToUnicode + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $info = new Info(); + $labels = self::process((string) $domainName, [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE), + ], $info); + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @param string $label + * + * @return bool + */ + private static function isValidContextJ(array $codePoints, $label) + { + if (!isset(self::$virama)) { + self::$virama = require __DIR__.\DIRECTORY_SEPARATOR.'Resources'.\DIRECTORY_SEPARATOR.'unidata'.\DIRECTORY_SEPARATOR.'virama.php'; + } + + $offset = 0; + + foreach ($codePoints as $i => $codePoint) { + if (0x200C !== $codePoint && 0x200D !== $codePoint) { + continue; + } + + if (!isset($codePoints[$i - 1])) { + return false; + } + + // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True; + if (isset(self::$virama[$codePoints[$i - 1]])) { + continue; + } + + // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then + // True; + // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] + if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { + $offset += \strlen($matches[1][0]); + + continue; + } + + return false; + } + + return true; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap + * + * @param string $input + * @param array $options + * + * @return string + */ + private static function mapCodePoints($input, array $options, Info $info) + { + $str = ''; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + $transitional = $options['Transitional_Processing']; + + foreach (self::utf8Decode($input) as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + + switch ($data['status']) { + case 'disallowed': + $info->errors |= self::ERROR_DISALLOWED; + + // no break. + + case 'valid': + $str .= mb_chr($codePoint, 'utf-8'); + + break; + + case 'ignored': + // Do nothing. + break; + + case 'mapped': + $str .= $data['mapping']; + + break; + + case 'deviation': + $info->transitionalDifferent = true; + $str .= ($transitional ? $data['mapping'] : mb_chr($codePoint, 'utf-8')); + + break; + } + } + + return $str; + } + + /** + * @see https://www.unicode.org/reports/tr46/#Processing + * + * @param string $domain + * @param array $options + * + * @return array + */ + private static function process($domain, array $options, Info $info) + { + // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and + // we need to respect the VerifyDnsLength option. + $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength']; + + if ($checkForEmptyLabels && '' === $domain) { + $info->errors |= self::ERROR_EMPTY_LABEL; + + return [$domain]; + } + + // Step 1. Map each code point in the domain name string + $domain = self::mapCodePoints($domain, $options, $info); + + // Step 2. Normalize the domain name string to Unicode Normalization Form C. + if (!Normalizer::isNormalized($domain, Normalizer::FORM_C)) { + $domain = Normalizer::normalize($domain, Normalizer::FORM_C); + } + + // Step 3. Break the string into labels at U+002E (.) FULL STOP. + $labels = explode('.', $domain); + $lastLabelIndex = \count($labels) - 1; + + // Step 4. Convert and validate each label in the domain name string. + foreach ($labels as $i => $label) { + $validationOptions = $options; + + if ('xn--' === substr($label, 0, 4)) { + try { + $label = self::punycodeDecode(substr($label, 4)); + } catch (Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + + continue; + } + + $validationOptions['Transitional_Processing'] = false; + $labels[$i] = $label; + } + + self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex); + } + + if ($info->bidiDomain && !$info->validBidiDomain) { + $info->errors |= self::ERROR_BIDI; + } + + // Any input domain name string that does not record an error has been successfully + // processed according to this specification. Conversely, if an input domain_name string + // causes an error, then the processing of the input domain_name string fails. Determining + // what to do with error input is up to the caller, and not in the scope of this document. + return $labels; + } + + /** + * @see https://tools.ietf.org/html/rfc5893#section-2 + * + * @param string $label + */ + private static function validateBidiLabel($label, Info $info) + { + if (1 === preg_match(Regex::RTL_LABEL, $label)) { + $info->bidiDomain = true; + + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the R or AL property, it is an RTL label + if (1 !== preg_match(Regex::BIDI_STEP_1_RTL, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, + // CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_2, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 3. In an RTL label, the end of the label must be a character with Bidi property + // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_3, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. + if (1 === preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === preg_match(Regex::BIDI_STEP_4_EN, $label)) { + $info->validBidiDomain = false; + + return; + } + + return; + } + + // We are a LTR label + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the L property, it is an LTR label. + if (1 !== preg_match(Regex::BIDI_STEP_1_LTR, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 5. In an LTR label, only characters with the Bidi properties L, EN, + // ES, CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_5, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or + // EN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_6, $label)) { + $info->validBidiDomain = false; + + return; + } + } + + /** + * @param array $labels + */ + private static function validateDomainAndLabelLength(array $labels, Info $info) + { + $maxDomainSize = self::MAX_DOMAIN_SIZE; + $length = \count($labels); + + // Number of "." delimiters. + $domainLength = $length - 1; + + // If the last label is empty and it is not the first label, then it is the root label. + // Increase the max size by 1, making it 254, to account for the root label's "." + // delimiter. This also means we don't need to check the last label's length for being too + // long. + if ($length > 1 && '' === $labels[$length - 1]) { + ++$maxDomainSize; + --$length; + } + + for ($i = 0; $i < $length; ++$i) { + $bytes = \strlen($labels[$i]); + $domainLength += $bytes; + + if ($bytes > self::MAX_LABEL_SIZE) { + $info->errors |= self::ERROR_LABEL_TOO_LONG; + } + } + + if ($domainLength > $maxDomainSize) { + $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG; + } + } + + /** + * @see https://www.unicode.org/reports/tr46/#Validity_Criteria + * + * @param string $label + * @param array $options + * @param bool $canBeEmpty + */ + private static function validateLabel($label, Info $info, array $options, $canBeEmpty) + { + if ('' === $label) { + if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) { + $info->errors |= self::ERROR_EMPTY_LABEL; + } + + return; + } + + // Step 1. The label must be in Unicode Normalization Form C. + if (!Normalizer::isNormalized($label, Normalizer::FORM_C)) { + $info->errors |= self::ERROR_INVALID_ACE_LABEL; + } + + $codePoints = self::utf8Decode($label); + + if ($options['CheckHyphens']) { + // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character + // in both the thrid and fourth positions. + if (isset($codePoints[2], $codePoints[3]) && 0x002D === $codePoints[2] && 0x002D === $codePoints[3]) { + $info->errors |= self::ERROR_HYPHEN_3_4; + } + + // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D + // HYPHEN-MINUS character. + if ('-' === substr($label, 0, 1)) { + $info->errors |= self::ERROR_LEADING_HYPHEN; + } + + if ('-' === substr($label, -1, 1)) { + $info->errors |= self::ERROR_TRAILING_HYPHEN; + } + } + + // Step 4. The label must not contain a U+002E (.) FULL STOP. + if (false !== strpos($label, '.')) { + $info->errors |= self::ERROR_LABEL_HAS_DOT; + } + + // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark. + if (1 === preg_match(Regex::COMBINING_MARK, $label)) { + $info->errors |= self::ERROR_LEADING_COMBINING_MARK; + } + + // Step 6. Each code point in the label must only have certain status values according to + // Section 5, IDNA Mapping Table: + $transitional = $options['Transitional_Processing']; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + + foreach ($codePoints as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + $status = $data['status']; + + if ('valid' === $status || (!$transitional && 'deviation' === $status)) { + continue; + } + + $info->errors |= self::ERROR_DISALLOWED; + + break; + } + + // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in + // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA) + // [IDNA2008]. + if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) { + $info->errors |= self::ERROR_CONTEXTJ; + } + + // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must + // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2. + if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) { + self::validateBidiLabel($label, $info); + } + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.2 + * + * @param string $input + * + * @return string + */ + private static function punycodeDecode($input) + { + $n = self::INITIAL_N; + $out = 0; + $i = 0; + $bias = self::INITIAL_BIAS; + $lastDelimIndex = strrpos($input, self::DELIMITER); + $b = false === $lastDelimIndex ? 0 : $lastDelimIndex; + $inputLength = \strlen($input); + $output = []; + $bytes = array_map('ord', str_split($input)); + + for ($j = 0; $j < $b; ++$j) { + if ($bytes[$j] > 0x7F) { + throw new Exception('Invalid input'); + } + + $output[$out++] = $input[$j]; + } + + if ($b > 0) { + ++$b; + } + + for ($in = $b; $in < $inputLength; ++$out) { + $oldi = $i; + $w = 1; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($in >= $inputLength) { + throw new Exception('Invalid input'); + } + + $digit = self::$basicToDigit[$bytes[$in++] & 0xFF]; + + if ($digit < 0) { + throw new Exception('Invalid input'); + } + + if ($digit > intdiv(self::MAX_INT - $i, $w)) { + throw new Exception('Integer overflow'); + } + + $i += $digit * $w; + + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($digit < $t) { + break; + } + + $baseMinusT = self::BASE - $t; + + if ($w > intdiv(self::MAX_INT, $baseMinusT)) { + throw new Exception('Integer overflow'); + } + + $w *= $baseMinusT; + } + + $outPlusOne = $out + 1; + $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi); + + if (intdiv($i, $outPlusOne) > self::MAX_INT - $n) { + throw new Exception('Integer overflow'); + } + + $n += intdiv($i, $outPlusOne); + $i %= $outPlusOne; + array_splice($output, $i++, 0, [mb_chr($n, 'utf-8')]); + } + + return implode('', $output); + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.3 + * + * @param string $input + * + * @return string + */ + private static function punycodeEncode($input) + { + $n = self::INITIAL_N; + $delta = 0; + $out = 0; + $bias = self::INITIAL_BIAS; + $inputLength = 0; + $output = ''; + $iter = self::utf8Decode($input); + + foreach ($iter as $codePoint) { + ++$inputLength; + + if ($codePoint < 0x80) { + $output .= \chr($codePoint); + ++$out; + } + } + + $h = $out; + $b = $out; + + if ($b > 0) { + $output .= self::DELIMITER; + ++$out; + } + + while ($h < $inputLength) { + $m = self::MAX_INT; + + foreach ($iter as $codePoint) { + if ($codePoint >= $n && $codePoint < $m) { + $m = $codePoint; + } + } + + if ($m - $n > intdiv(self::MAX_INT - $delta, $h + 1)) { + throw new Exception('Integer overflow'); + } + + $delta += ($m - $n) * ($h + 1); + $n = $m; + + foreach ($iter as $codePoint) { + if ($codePoint < $n && 0 === ++$delta) { + throw new Exception('Integer overflow'); + } + + if ($codePoint === $n) { + $q = $delta; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($q < $t) { + break; + } + + $qMinusT = $q - $t; + $baseMinusT = self::BASE - $t; + $output .= self::encodeDigit($t + ($qMinusT) % ($baseMinusT), false); + ++$out; + $q = intdiv($qMinusT, $baseMinusT); + } + + $output .= self::encodeDigit($q, false); + ++$out; + $bias = self::adaptBias($delta, $h + 1, $h === $b); + $delta = 0; + ++$h; + } + } + + ++$delta; + ++$n; + } + + return $output; + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.1 + * + * @param int $delta + * @param int $numPoints + * @param bool $firstTime + * + * @return int + */ + private static function adaptBias($delta, $numPoints, $firstTime) + { + // xxx >> 1 is a faster way of doing intdiv(xxx, 2) + $delta = $firstTime ? intdiv($delta, self::DAMP) : $delta >> 1; + $delta += intdiv($delta, $numPoints); + $k = 0; + + while ($delta > ((self::BASE - self::TMIN) * self::TMAX) >> 1) { + $delta = intdiv($delta, self::BASE - self::TMIN); + $k += self::BASE; + } + + return $k + intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW); + } + + /** + * @param int $d + * @param bool $flag + * + * @return string + */ + private static function encodeDigit($d, $flag) + { + return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5)); + } + + /** + * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any + * invalid byte sequences will be replaced by a U+FFFD replacement code point. + * + * @see https://encoding.spec.whatwg.org/#utf-8-decoder + * + * @param string $input + * + * @return array + */ + private static function utf8Decode($input) + { + $bytesSeen = 0; + $bytesNeeded = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = 0; + $codePoints = []; + $length = \strlen($input); + + for ($i = 0; $i < $length; ++$i) { + $byte = \ord($input[$i]); + + if (0 === $bytesNeeded) { + if ($byte >= 0x00 && $byte <= 0x7F) { + $codePoints[] = $byte; + + continue; + } + + if ($byte >= 0xC2 && $byte <= 0xDF) { + $bytesNeeded = 1; + $codePoint = $byte & 0x1F; + } elseif ($byte >= 0xE0 && $byte <= 0xEF) { + if (0xE0 === $byte) { + $lowerBoundary = 0xA0; + } elseif (0xED === $byte) { + $upperBoundary = 0x9F; + } + + $bytesNeeded = 2; + $codePoint = $byte & 0xF; + } elseif ($byte >= 0xF0 && $byte <= 0xF4) { + if (0xF0 === $byte) { + $lowerBoundary = 0x90; + } elseif (0xF4 === $byte) { + $upperBoundary = 0x8F; + } + + $bytesNeeded = 3; + $codePoint = $byte & 0x7; + } else { + $codePoints[] = 0xFFFD; + } + + continue; + } + + if ($byte < $lowerBoundary || $byte > $upperBoundary) { + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + --$i; + $codePoints[] = 0xFFFD; + + continue; + } + + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = ($codePoint << 6) | ($byte & 0x3F); + + if (++$bytesSeen !== $bytesNeeded) { + continue; + } + + $codePoints[] = $codePoint; + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + } + + // String unexpectedly ended, so append a U+FFFD code point. + if (0 !== $bytesNeeded) { + $codePoints[] = 0xFFFD; + } + + return $codePoints; + } + + /** + * @param int $codePoint + * @param bool $useSTD3ASCIIRules + * + * @return array{status: string, mapping?: string} + */ + private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) + { + if (!self::$mappingTableLoaded) { + self::$mappingTableLoaded = true; + self::$mapped = require __DIR__.'/Resources/unidata/mapped.php'; + self::$ignored = require __DIR__.'/Resources/unidata/ignored.php'; + self::$deviation = require __DIR__.'/Resources/unidata/deviation.php'; + self::$disallowed = require __DIR__.'/Resources/unidata/disallowed.php'; + self::$disallowed_STD3_mapped = require __DIR__.'/Resources/unidata/disallowed_STD3_mapped.php'; + self::$disallowed_STD3_valid = require __DIR__.'/Resources/unidata/disallowed_STD3_valid.php'; + } + + if (isset(self::$mapped[$codePoint])) { + return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; + } + + if (isset(self::$ignored[$codePoint])) { + return ['status' => 'ignored']; + } + + if (isset(self::$deviation[$codePoint])) { + return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; + } + + if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { + return ['status' => 'disallowed']; + } + + $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); + + if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) { + $status = 'disallowed'; + + if (!$useSTD3ASCIIRules) { + $status = $isDisallowedMapped ? 'mapped' : 'valid'; + } + + if ($isDisallowedMapped) { + return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; + } + + return ['status' => $status]; + } + + return ['status' => 'valid']; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Info.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Info.php new file mode 100644 index 0000000..25c3582 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Info.php @@ -0,0 +1,23 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +/** + * @internal + */ +class Info +{ + public $bidiDomain = false; + public $errors = 0; + public $validBidiDomain = true; + public $transitionalDifferent = false; +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/LICENSE b/plugins/email/vendor/symfony/polyfill-intl-idn/LICENSE new file mode 100644 index 0000000..03c5e25 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier and Trevor Rowbotham + +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/plugins/email/vendor/symfony/polyfill-intl-idn/README.md b/plugins/email/vendor/symfony/polyfill-intl-idn/README.md new file mode 100644 index 0000000..2e75f2e --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Intl: Idn +============================ + +This component provides [`idn_to_ascii`](https://php.net/idn-to-ascii) and [`idn_to_utf8`](https://php.net/idn-to-utf8) functions to users who run php versions without the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php new file mode 100644 index 0000000..5bb70e4 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php @@ -0,0 +1,375 @@ += 128 && $codePoint <= 159) { + return true; + } + + if ($codePoint >= 2155 && $codePoint <= 2207) { + return true; + } + + if ($codePoint >= 3676 && $codePoint <= 3712) { + return true; + } + + if ($codePoint >= 3808 && $codePoint <= 3839) { + return true; + } + + if ($codePoint >= 4059 && $codePoint <= 4095) { + return true; + } + + if ($codePoint >= 4256 && $codePoint <= 4293) { + return true; + } + + if ($codePoint >= 6849 && $codePoint <= 6911) { + return true; + } + + if ($codePoint >= 11859 && $codePoint <= 11903) { + return true; + } + + if ($codePoint >= 42955 && $codePoint <= 42996) { + return true; + } + + if ($codePoint >= 55296 && $codePoint <= 57343) { + return true; + } + + if ($codePoint >= 57344 && $codePoint <= 63743) { + return true; + } + + if ($codePoint >= 64218 && $codePoint <= 64255) { + return true; + } + + if ($codePoint >= 64976 && $codePoint <= 65007) { + return true; + } + + if ($codePoint >= 65630 && $codePoint <= 65663) { + return true; + } + + if ($codePoint >= 65953 && $codePoint <= 65999) { + return true; + } + + if ($codePoint >= 66046 && $codePoint <= 66175) { + return true; + } + + if ($codePoint >= 66518 && $codePoint <= 66559) { + return true; + } + + if ($codePoint >= 66928 && $codePoint <= 67071) { + return true; + } + + if ($codePoint >= 67432 && $codePoint <= 67583) { + return true; + } + + if ($codePoint >= 67760 && $codePoint <= 67807) { + return true; + } + + if ($codePoint >= 67904 && $codePoint <= 67967) { + return true; + } + + if ($codePoint >= 68256 && $codePoint <= 68287) { + return true; + } + + if ($codePoint >= 68528 && $codePoint <= 68607) { + return true; + } + + if ($codePoint >= 68681 && $codePoint <= 68735) { + return true; + } + + if ($codePoint >= 68922 && $codePoint <= 69215) { + return true; + } + + if ($codePoint >= 69298 && $codePoint <= 69375) { + return true; + } + + if ($codePoint >= 69466 && $codePoint <= 69551) { + return true; + } + + if ($codePoint >= 70207 && $codePoint <= 70271) { + return true; + } + + if ($codePoint >= 70517 && $codePoint <= 70655) { + return true; + } + + if ($codePoint >= 70874 && $codePoint <= 71039) { + return true; + } + + if ($codePoint >= 71134 && $codePoint <= 71167) { + return true; + } + + if ($codePoint >= 71370 && $codePoint <= 71423) { + return true; + } + + if ($codePoint >= 71488 && $codePoint <= 71679) { + return true; + } + + if ($codePoint >= 71740 && $codePoint <= 71839) { + return true; + } + + if ($codePoint >= 72026 && $codePoint <= 72095) { + return true; + } + + if ($codePoint >= 72441 && $codePoint <= 72703) { + return true; + } + + if ($codePoint >= 72887 && $codePoint <= 72959) { + return true; + } + + if ($codePoint >= 73130 && $codePoint <= 73439) { + return true; + } + + if ($codePoint >= 73465 && $codePoint <= 73647) { + return true; + } + + if ($codePoint >= 74650 && $codePoint <= 74751) { + return true; + } + + if ($codePoint >= 75076 && $codePoint <= 77823) { + return true; + } + + if ($codePoint >= 78905 && $codePoint <= 82943) { + return true; + } + + if ($codePoint >= 83527 && $codePoint <= 92159) { + return true; + } + + if ($codePoint >= 92784 && $codePoint <= 92879) { + return true; + } + + if ($codePoint >= 93072 && $codePoint <= 93759) { + return true; + } + + if ($codePoint >= 93851 && $codePoint <= 93951) { + return true; + } + + if ($codePoint >= 94112 && $codePoint <= 94175) { + return true; + } + + if ($codePoint >= 101590 && $codePoint <= 101631) { + return true; + } + + if ($codePoint >= 101641 && $codePoint <= 110591) { + return true; + } + + if ($codePoint >= 110879 && $codePoint <= 110927) { + return true; + } + + if ($codePoint >= 111356 && $codePoint <= 113663) { + return true; + } + + if ($codePoint >= 113828 && $codePoint <= 118783) { + return true; + } + + if ($codePoint >= 119366 && $codePoint <= 119519) { + return true; + } + + if ($codePoint >= 119673 && $codePoint <= 119807) { + return true; + } + + if ($codePoint >= 121520 && $codePoint <= 122879) { + return true; + } + + if ($codePoint >= 122923 && $codePoint <= 123135) { + return true; + } + + if ($codePoint >= 123216 && $codePoint <= 123583) { + return true; + } + + if ($codePoint >= 123648 && $codePoint <= 124927) { + return true; + } + + if ($codePoint >= 125143 && $codePoint <= 125183) { + return true; + } + + if ($codePoint >= 125280 && $codePoint <= 126064) { + return true; + } + + if ($codePoint >= 126133 && $codePoint <= 126208) { + return true; + } + + if ($codePoint >= 126270 && $codePoint <= 126463) { + return true; + } + + if ($codePoint >= 126652 && $codePoint <= 126703) { + return true; + } + + if ($codePoint >= 126706 && $codePoint <= 126975) { + return true; + } + + if ($codePoint >= 127406 && $codePoint <= 127461) { + return true; + } + + if ($codePoint >= 127590 && $codePoint <= 127743) { + return true; + } + + if ($codePoint >= 129202 && $codePoint <= 129279) { + return true; + } + + if ($codePoint >= 129751 && $codePoint <= 129791) { + return true; + } + + if ($codePoint >= 129995 && $codePoint <= 130031) { + return true; + } + + if ($codePoint >= 130042 && $codePoint <= 131069) { + return true; + } + + if ($codePoint >= 173790 && $codePoint <= 173823) { + return true; + } + + if ($codePoint >= 191457 && $codePoint <= 194559) { + return true; + } + + if ($codePoint >= 195102 && $codePoint <= 196605) { + return true; + } + + if ($codePoint >= 201547 && $codePoint <= 262141) { + return true; + } + + if ($codePoint >= 262144 && $codePoint <= 327677) { + return true; + } + + if ($codePoint >= 327680 && $codePoint <= 393213) { + return true; + } + + if ($codePoint >= 393216 && $codePoint <= 458749) { + return true; + } + + if ($codePoint >= 458752 && $codePoint <= 524285) { + return true; + } + + if ($codePoint >= 524288 && $codePoint <= 589821) { + return true; + } + + if ($codePoint >= 589824 && $codePoint <= 655357) { + return true; + } + + if ($codePoint >= 655360 && $codePoint <= 720893) { + return true; + } + + if ($codePoint >= 720896 && $codePoint <= 786429) { + return true; + } + + if ($codePoint >= 786432 && $codePoint <= 851965) { + return true; + } + + if ($codePoint >= 851968 && $codePoint <= 917501) { + return true; + } + + if ($codePoint >= 917536 && $codePoint <= 917631) { + return true; + } + + if ($codePoint >= 917632 && $codePoint <= 917759) { + return true; + } + + if ($codePoint >= 918000 && $codePoint <= 983037) { + return true; + } + + if ($codePoint >= 983040 && $codePoint <= 1048573) { + return true; + } + + if ($codePoint >= 1048576 && $codePoint <= 1114109) { + return true; + } + + return false; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php new file mode 100644 index 0000000..c4b9778 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php @@ -0,0 +1,24 @@ + 'ss', + 962 => 'σ', + 8204 => '', + 8205 => '', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php new file mode 100644 index 0000000..25a5f56 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php @@ -0,0 +1,2638 @@ + true, + 889 => true, + 896 => true, + 897 => true, + 898 => true, + 899 => true, + 907 => true, + 909 => true, + 930 => true, + 1216 => true, + 1328 => true, + 1367 => true, + 1368 => true, + 1419 => true, + 1420 => true, + 1424 => true, + 1480 => true, + 1481 => true, + 1482 => true, + 1483 => true, + 1484 => true, + 1485 => true, + 1486 => true, + 1487 => true, + 1515 => true, + 1516 => true, + 1517 => true, + 1518 => true, + 1525 => true, + 1526 => true, + 1527 => true, + 1528 => true, + 1529 => true, + 1530 => true, + 1531 => true, + 1532 => true, + 1533 => true, + 1534 => true, + 1535 => true, + 1536 => true, + 1537 => true, + 1538 => true, + 1539 => true, + 1540 => true, + 1541 => true, + 1564 => true, + 1565 => true, + 1757 => true, + 1806 => true, + 1807 => true, + 1867 => true, + 1868 => true, + 1970 => true, + 1971 => true, + 1972 => true, + 1973 => true, + 1974 => true, + 1975 => true, + 1976 => true, + 1977 => true, + 1978 => true, + 1979 => true, + 1980 => true, + 1981 => true, + 1982 => true, + 1983 => true, + 2043 => true, + 2044 => true, + 2094 => true, + 2095 => true, + 2111 => true, + 2140 => true, + 2141 => true, + 2143 => true, + 2229 => true, + 2248 => true, + 2249 => true, + 2250 => true, + 2251 => true, + 2252 => true, + 2253 => true, + 2254 => true, + 2255 => true, + 2256 => true, + 2257 => true, + 2258 => true, + 2274 => true, + 2436 => true, + 2445 => true, + 2446 => true, + 2449 => true, + 2450 => true, + 2473 => true, + 2481 => true, + 2483 => true, + 2484 => true, + 2485 => true, + 2490 => true, + 2491 => true, + 2501 => true, + 2502 => true, + 2505 => true, + 2506 => true, + 2511 => true, + 2512 => true, + 2513 => true, + 2514 => true, + 2515 => true, + 2516 => true, + 2517 => true, + 2518 => true, + 2520 => true, + 2521 => true, + 2522 => true, + 2523 => true, + 2526 => true, + 2532 => true, + 2533 => true, + 2559 => true, + 2560 => true, + 2564 => true, + 2571 => true, + 2572 => true, + 2573 => true, + 2574 => true, + 2577 => true, + 2578 => true, + 2601 => true, + 2609 => true, + 2612 => true, + 2615 => true, + 2618 => true, + 2619 => true, + 2621 => true, + 2627 => true, + 2628 => true, + 2629 => true, + 2630 => true, + 2633 => true, + 2634 => true, + 2638 => true, + 2639 => true, + 2640 => true, + 2642 => true, + 2643 => true, + 2644 => true, + 2645 => true, + 2646 => true, + 2647 => true, + 2648 => true, + 2653 => true, + 2655 => true, + 2656 => true, + 2657 => true, + 2658 => true, + 2659 => true, + 2660 => true, + 2661 => true, + 2679 => true, + 2680 => true, + 2681 => true, + 2682 => true, + 2683 => true, + 2684 => true, + 2685 => true, + 2686 => true, + 2687 => true, + 2688 => true, + 2692 => true, + 2702 => true, + 2706 => true, + 2729 => true, + 2737 => true, + 2740 => true, + 2746 => true, + 2747 => true, + 2758 => true, + 2762 => true, + 2766 => true, + 2767 => true, + 2769 => true, + 2770 => true, + 2771 => true, + 2772 => true, + 2773 => true, + 2774 => true, + 2775 => true, + 2776 => true, + 2777 => true, + 2778 => true, + 2779 => true, + 2780 => true, + 2781 => true, + 2782 => true, + 2783 => true, + 2788 => true, + 2789 => true, + 2802 => true, + 2803 => true, + 2804 => true, + 2805 => true, + 2806 => true, + 2807 => true, + 2808 => true, + 2816 => true, + 2820 => true, + 2829 => true, + 2830 => true, + 2833 => true, + 2834 => true, + 2857 => true, + 2865 => true, + 2868 => true, + 2874 => true, + 2875 => true, + 2885 => true, + 2886 => true, + 2889 => true, + 2890 => true, + 2894 => true, + 2895 => true, + 2896 => true, + 2897 => true, + 2898 => true, + 2899 => true, + 2900 => true, + 2904 => true, + 2905 => true, + 2906 => true, + 2907 => true, + 2910 => true, + 2916 => true, + 2917 => true, + 2936 => true, + 2937 => true, + 2938 => true, + 2939 => true, + 2940 => true, + 2941 => true, + 2942 => true, + 2943 => true, + 2944 => true, + 2945 => true, + 2948 => true, + 2955 => true, + 2956 => true, + 2957 => true, + 2961 => true, + 2966 => true, + 2967 => true, + 2968 => true, + 2971 => true, + 2973 => true, + 2976 => true, + 2977 => true, + 2978 => true, + 2981 => true, + 2982 => true, + 2983 => true, + 2987 => true, + 2988 => true, + 2989 => true, + 3002 => true, + 3003 => true, + 3004 => true, + 3005 => true, + 3011 => true, + 3012 => true, + 3013 => true, + 3017 => true, + 3022 => true, + 3023 => true, + 3025 => true, + 3026 => true, + 3027 => true, + 3028 => true, + 3029 => true, + 3030 => true, + 3032 => true, + 3033 => true, + 3034 => true, + 3035 => true, + 3036 => true, + 3037 => true, + 3038 => true, + 3039 => true, + 3040 => true, + 3041 => true, + 3042 => true, + 3043 => true, + 3044 => true, + 3045 => true, + 3067 => true, + 3068 => true, + 3069 => true, + 3070 => true, + 3071 => true, + 3085 => true, + 3089 => true, + 3113 => true, + 3130 => true, + 3131 => true, + 3132 => true, + 3141 => true, + 3145 => true, + 3150 => true, + 3151 => true, + 3152 => true, + 3153 => true, + 3154 => true, + 3155 => true, + 3156 => true, + 3159 => true, + 3163 => true, + 3164 => true, + 3165 => true, + 3166 => true, + 3167 => true, + 3172 => true, + 3173 => true, + 3184 => true, + 3185 => true, + 3186 => true, + 3187 => true, + 3188 => true, + 3189 => true, + 3190 => true, + 3213 => true, + 3217 => true, + 3241 => true, + 3252 => true, + 3258 => true, + 3259 => true, + 3269 => true, + 3273 => true, + 3278 => true, + 3279 => true, + 3280 => true, + 3281 => true, + 3282 => true, + 3283 => true, + 3284 => true, + 3287 => true, + 3288 => true, + 3289 => true, + 3290 => true, + 3291 => true, + 3292 => true, + 3293 => true, + 3295 => true, + 3300 => true, + 3301 => true, + 3312 => true, + 3315 => true, + 3316 => true, + 3317 => true, + 3318 => true, + 3319 => true, + 3320 => true, + 3321 => true, + 3322 => true, + 3323 => true, + 3324 => true, + 3325 => true, + 3326 => true, + 3327 => true, + 3341 => true, + 3345 => true, + 3397 => true, + 3401 => true, + 3408 => true, + 3409 => true, + 3410 => true, + 3411 => true, + 3428 => true, + 3429 => true, + 3456 => true, + 3460 => true, + 3479 => true, + 3480 => true, + 3481 => true, + 3506 => true, + 3516 => true, + 3518 => true, + 3519 => true, + 3527 => true, + 3528 => true, + 3529 => true, + 3531 => true, + 3532 => true, + 3533 => true, + 3534 => true, + 3541 => true, + 3543 => true, + 3552 => true, + 3553 => true, + 3554 => true, + 3555 => true, + 3556 => true, + 3557 => true, + 3568 => true, + 3569 => true, + 3573 => true, + 3574 => true, + 3575 => true, + 3576 => true, + 3577 => true, + 3578 => true, + 3579 => true, + 3580 => true, + 3581 => true, + 3582 => true, + 3583 => true, + 3584 => true, + 3643 => true, + 3644 => true, + 3645 => true, + 3646 => true, + 3715 => true, + 3717 => true, + 3723 => true, + 3748 => true, + 3750 => true, + 3774 => true, + 3775 => true, + 3781 => true, + 3783 => true, + 3790 => true, + 3791 => true, + 3802 => true, + 3803 => true, + 3912 => true, + 3949 => true, + 3950 => true, + 3951 => true, + 3952 => true, + 3992 => true, + 4029 => true, + 4045 => true, + 4294 => true, + 4296 => true, + 4297 => true, + 4298 => true, + 4299 => true, + 4300 => true, + 4302 => true, + 4303 => true, + 4447 => true, + 4448 => true, + 4681 => true, + 4686 => true, + 4687 => true, + 4695 => true, + 4697 => true, + 4702 => true, + 4703 => true, + 4745 => true, + 4750 => true, + 4751 => true, + 4785 => true, + 4790 => true, + 4791 => true, + 4799 => true, + 4801 => true, + 4806 => true, + 4807 => true, + 4823 => true, + 4881 => true, + 4886 => true, + 4887 => true, + 4955 => true, + 4956 => true, + 4989 => true, + 4990 => true, + 4991 => true, + 5018 => true, + 5019 => true, + 5020 => true, + 5021 => true, + 5022 => true, + 5023 => true, + 5110 => true, + 5111 => true, + 5118 => true, + 5119 => true, + 5760 => true, + 5789 => true, + 5790 => true, + 5791 => true, + 5881 => true, + 5882 => true, + 5883 => true, + 5884 => true, + 5885 => true, + 5886 => true, + 5887 => true, + 5901 => true, + 5909 => true, + 5910 => true, + 5911 => true, + 5912 => true, + 5913 => true, + 5914 => true, + 5915 => true, + 5916 => true, + 5917 => true, + 5918 => true, + 5919 => true, + 5943 => true, + 5944 => true, + 5945 => true, + 5946 => true, + 5947 => true, + 5948 => true, + 5949 => true, + 5950 => true, + 5951 => true, + 5972 => true, + 5973 => true, + 5974 => true, + 5975 => true, + 5976 => true, + 5977 => true, + 5978 => true, + 5979 => true, + 5980 => true, + 5981 => true, + 5982 => true, + 5983 => true, + 5997 => true, + 6001 => true, + 6004 => true, + 6005 => true, + 6006 => true, + 6007 => true, + 6008 => true, + 6009 => true, + 6010 => true, + 6011 => true, + 6012 => true, + 6013 => true, + 6014 => true, + 6015 => true, + 6068 => true, + 6069 => true, + 6110 => true, + 6111 => true, + 6122 => true, + 6123 => true, + 6124 => true, + 6125 => true, + 6126 => true, + 6127 => true, + 6138 => true, + 6139 => true, + 6140 => true, + 6141 => true, + 6142 => true, + 6143 => true, + 6150 => true, + 6158 => true, + 6159 => true, + 6170 => true, + 6171 => true, + 6172 => true, + 6173 => true, + 6174 => true, + 6175 => true, + 6265 => true, + 6266 => true, + 6267 => true, + 6268 => true, + 6269 => true, + 6270 => true, + 6271 => true, + 6315 => true, + 6316 => true, + 6317 => true, + 6318 => true, + 6319 => true, + 6390 => true, + 6391 => true, + 6392 => true, + 6393 => true, + 6394 => true, + 6395 => true, + 6396 => true, + 6397 => true, + 6398 => true, + 6399 => true, + 6431 => true, + 6444 => true, + 6445 => true, + 6446 => true, + 6447 => true, + 6460 => true, + 6461 => true, + 6462 => true, + 6463 => true, + 6465 => true, + 6466 => true, + 6467 => true, + 6510 => true, + 6511 => true, + 6517 => true, + 6518 => true, + 6519 => true, + 6520 => true, + 6521 => true, + 6522 => true, + 6523 => true, + 6524 => true, + 6525 => true, + 6526 => true, + 6527 => true, + 6572 => true, + 6573 => true, + 6574 => true, + 6575 => true, + 6602 => true, + 6603 => true, + 6604 => true, + 6605 => true, + 6606 => true, + 6607 => true, + 6619 => true, + 6620 => true, + 6621 => true, + 6684 => true, + 6685 => true, + 6751 => true, + 6781 => true, + 6782 => true, + 6794 => true, + 6795 => true, + 6796 => true, + 6797 => true, + 6798 => true, + 6799 => true, + 6810 => true, + 6811 => true, + 6812 => true, + 6813 => true, + 6814 => true, + 6815 => true, + 6830 => true, + 6831 => true, + 6988 => true, + 6989 => true, + 6990 => true, + 6991 => true, + 7037 => true, + 7038 => true, + 7039 => true, + 7156 => true, + 7157 => true, + 7158 => true, + 7159 => true, + 7160 => true, + 7161 => true, + 7162 => true, + 7163 => true, + 7224 => true, + 7225 => true, + 7226 => true, + 7242 => true, + 7243 => true, + 7244 => true, + 7305 => true, + 7306 => true, + 7307 => true, + 7308 => true, + 7309 => true, + 7310 => true, + 7311 => true, + 7355 => true, + 7356 => true, + 7368 => true, + 7369 => true, + 7370 => true, + 7371 => true, + 7372 => true, + 7373 => true, + 7374 => true, + 7375 => true, + 7419 => true, + 7420 => true, + 7421 => true, + 7422 => true, + 7423 => true, + 7674 => true, + 7958 => true, + 7959 => true, + 7966 => true, + 7967 => true, + 8006 => true, + 8007 => true, + 8014 => true, + 8015 => true, + 8024 => true, + 8026 => true, + 8028 => true, + 8030 => true, + 8062 => true, + 8063 => true, + 8117 => true, + 8133 => true, + 8148 => true, + 8149 => true, + 8156 => true, + 8176 => true, + 8177 => true, + 8181 => true, + 8191 => true, + 8206 => true, + 8207 => true, + 8228 => true, + 8229 => true, + 8230 => true, + 8232 => true, + 8233 => true, + 8234 => true, + 8235 => true, + 8236 => true, + 8237 => true, + 8238 => true, + 8289 => true, + 8290 => true, + 8291 => true, + 8293 => true, + 8294 => true, + 8295 => true, + 8296 => true, + 8297 => true, + 8298 => true, + 8299 => true, + 8300 => true, + 8301 => true, + 8302 => true, + 8303 => true, + 8306 => true, + 8307 => true, + 8335 => true, + 8349 => true, + 8350 => true, + 8351 => true, + 8384 => true, + 8385 => true, + 8386 => true, + 8387 => true, + 8388 => true, + 8389 => true, + 8390 => true, + 8391 => true, + 8392 => true, + 8393 => true, + 8394 => true, + 8395 => true, + 8396 => true, + 8397 => true, + 8398 => true, + 8399 => true, + 8433 => true, + 8434 => true, + 8435 => true, + 8436 => true, + 8437 => true, + 8438 => true, + 8439 => true, + 8440 => true, + 8441 => true, + 8442 => true, + 8443 => true, + 8444 => true, + 8445 => true, + 8446 => true, + 8447 => true, + 8498 => true, + 8579 => true, + 8588 => true, + 8589 => true, + 8590 => true, + 8591 => true, + 9255 => true, + 9256 => true, + 9257 => true, + 9258 => true, + 9259 => true, + 9260 => true, + 9261 => true, + 9262 => true, + 9263 => true, + 9264 => true, + 9265 => true, + 9266 => true, + 9267 => true, + 9268 => true, + 9269 => true, + 9270 => true, + 9271 => true, + 9272 => true, + 9273 => true, + 9274 => true, + 9275 => true, + 9276 => true, + 9277 => true, + 9278 => true, + 9279 => true, + 9291 => true, + 9292 => true, + 9293 => true, + 9294 => true, + 9295 => true, + 9296 => true, + 9297 => true, + 9298 => true, + 9299 => true, + 9300 => true, + 9301 => true, + 9302 => true, + 9303 => true, + 9304 => true, + 9305 => true, + 9306 => true, + 9307 => true, + 9308 => true, + 9309 => true, + 9310 => true, + 9311 => true, + 9352 => true, + 9353 => true, + 9354 => true, + 9355 => true, + 9356 => true, + 9357 => true, + 9358 => true, + 9359 => true, + 9360 => true, + 9361 => true, + 9362 => true, + 9363 => true, + 9364 => true, + 9365 => true, + 9366 => true, + 9367 => true, + 9368 => true, + 9369 => true, + 9370 => true, + 9371 => true, + 11124 => true, + 11125 => true, + 11158 => true, + 11311 => true, + 11359 => true, + 11508 => true, + 11509 => true, + 11510 => true, + 11511 => true, + 11512 => true, + 11558 => true, + 11560 => true, + 11561 => true, + 11562 => true, + 11563 => true, + 11564 => true, + 11566 => true, + 11567 => true, + 11624 => true, + 11625 => true, + 11626 => true, + 11627 => true, + 11628 => true, + 11629 => true, + 11630 => true, + 11633 => true, + 11634 => true, + 11635 => true, + 11636 => true, + 11637 => true, + 11638 => true, + 11639 => true, + 11640 => true, + 11641 => true, + 11642 => true, + 11643 => true, + 11644 => true, + 11645 => true, + 11646 => true, + 11671 => true, + 11672 => true, + 11673 => true, + 11674 => true, + 11675 => true, + 11676 => true, + 11677 => true, + 11678 => true, + 11679 => true, + 11687 => true, + 11695 => true, + 11703 => true, + 11711 => true, + 11719 => true, + 11727 => true, + 11735 => true, + 11743 => true, + 11930 => true, + 12020 => true, + 12021 => true, + 12022 => true, + 12023 => true, + 12024 => true, + 12025 => true, + 12026 => true, + 12027 => true, + 12028 => true, + 12029 => true, + 12030 => true, + 12031 => true, + 12246 => true, + 12247 => true, + 12248 => true, + 12249 => true, + 12250 => true, + 12251 => true, + 12252 => true, + 12253 => true, + 12254 => true, + 12255 => true, + 12256 => true, + 12257 => true, + 12258 => true, + 12259 => true, + 12260 => true, + 12261 => true, + 12262 => true, + 12263 => true, + 12264 => true, + 12265 => true, + 12266 => true, + 12267 => true, + 12268 => true, + 12269 => true, + 12270 => true, + 12271 => true, + 12272 => true, + 12273 => true, + 12274 => true, + 12275 => true, + 12276 => true, + 12277 => true, + 12278 => true, + 12279 => true, + 12280 => true, + 12281 => true, + 12282 => true, + 12283 => true, + 12284 => true, + 12285 => true, + 12286 => true, + 12287 => true, + 12352 => true, + 12439 => true, + 12440 => true, + 12544 => true, + 12545 => true, + 12546 => true, + 12547 => true, + 12548 => true, + 12592 => true, + 12644 => true, + 12687 => true, + 12772 => true, + 12773 => true, + 12774 => true, + 12775 => true, + 12776 => true, + 12777 => true, + 12778 => true, + 12779 => true, + 12780 => true, + 12781 => true, + 12782 => true, + 12783 => true, + 12831 => true, + 13250 => true, + 13255 => true, + 13272 => true, + 40957 => true, + 40958 => true, + 40959 => true, + 42125 => true, + 42126 => true, + 42127 => true, + 42183 => true, + 42184 => true, + 42185 => true, + 42186 => true, + 42187 => true, + 42188 => true, + 42189 => true, + 42190 => true, + 42191 => true, + 42540 => true, + 42541 => true, + 42542 => true, + 42543 => true, + 42544 => true, + 42545 => true, + 42546 => true, + 42547 => true, + 42548 => true, + 42549 => true, + 42550 => true, + 42551 => true, + 42552 => true, + 42553 => true, + 42554 => true, + 42555 => true, + 42556 => true, + 42557 => true, + 42558 => true, + 42559 => true, + 42744 => true, + 42745 => true, + 42746 => true, + 42747 => true, + 42748 => true, + 42749 => true, + 42750 => true, + 42751 => true, + 42944 => true, + 42945 => true, + 43053 => true, + 43054 => true, + 43055 => true, + 43066 => true, + 43067 => true, + 43068 => true, + 43069 => true, + 43070 => true, + 43071 => true, + 43128 => true, + 43129 => true, + 43130 => true, + 43131 => true, + 43132 => true, + 43133 => true, + 43134 => true, + 43135 => true, + 43206 => true, + 43207 => true, + 43208 => true, + 43209 => true, + 43210 => true, + 43211 => true, + 43212 => true, + 43213 => true, + 43226 => true, + 43227 => true, + 43228 => true, + 43229 => true, + 43230 => true, + 43231 => true, + 43348 => true, + 43349 => true, + 43350 => true, + 43351 => true, + 43352 => true, + 43353 => true, + 43354 => true, + 43355 => true, + 43356 => true, + 43357 => true, + 43358 => true, + 43389 => true, + 43390 => true, + 43391 => true, + 43470 => true, + 43482 => true, + 43483 => true, + 43484 => true, + 43485 => true, + 43519 => true, + 43575 => true, + 43576 => true, + 43577 => true, + 43578 => true, + 43579 => true, + 43580 => true, + 43581 => true, + 43582 => true, + 43583 => true, + 43598 => true, + 43599 => true, + 43610 => true, + 43611 => true, + 43715 => true, + 43716 => true, + 43717 => true, + 43718 => true, + 43719 => true, + 43720 => true, + 43721 => true, + 43722 => true, + 43723 => true, + 43724 => true, + 43725 => true, + 43726 => true, + 43727 => true, + 43728 => true, + 43729 => true, + 43730 => true, + 43731 => true, + 43732 => true, + 43733 => true, + 43734 => true, + 43735 => true, + 43736 => true, + 43737 => true, + 43738 => true, + 43767 => true, + 43768 => true, + 43769 => true, + 43770 => true, + 43771 => true, + 43772 => true, + 43773 => true, + 43774 => true, + 43775 => true, + 43776 => true, + 43783 => true, + 43784 => true, + 43791 => true, + 43792 => true, + 43799 => true, + 43800 => true, + 43801 => true, + 43802 => true, + 43803 => true, + 43804 => true, + 43805 => true, + 43806 => true, + 43807 => true, + 43815 => true, + 43823 => true, + 43884 => true, + 43885 => true, + 43886 => true, + 43887 => true, + 44014 => true, + 44015 => true, + 44026 => true, + 44027 => true, + 44028 => true, + 44029 => true, + 44030 => true, + 44031 => true, + 55204 => true, + 55205 => true, + 55206 => true, + 55207 => true, + 55208 => true, + 55209 => true, + 55210 => true, + 55211 => true, + 55212 => true, + 55213 => true, + 55214 => true, + 55215 => true, + 55239 => true, + 55240 => true, + 55241 => true, + 55242 => true, + 55292 => true, + 55293 => true, + 55294 => true, + 55295 => true, + 64110 => true, + 64111 => true, + 64263 => true, + 64264 => true, + 64265 => true, + 64266 => true, + 64267 => true, + 64268 => true, + 64269 => true, + 64270 => true, + 64271 => true, + 64272 => true, + 64273 => true, + 64274 => true, + 64280 => true, + 64281 => true, + 64282 => true, + 64283 => true, + 64284 => true, + 64311 => true, + 64317 => true, + 64319 => true, + 64322 => true, + 64325 => true, + 64450 => true, + 64451 => true, + 64452 => true, + 64453 => true, + 64454 => true, + 64455 => true, + 64456 => true, + 64457 => true, + 64458 => true, + 64459 => true, + 64460 => true, + 64461 => true, + 64462 => true, + 64463 => true, + 64464 => true, + 64465 => true, + 64466 => true, + 64832 => true, + 64833 => true, + 64834 => true, + 64835 => true, + 64836 => true, + 64837 => true, + 64838 => true, + 64839 => true, + 64840 => true, + 64841 => true, + 64842 => true, + 64843 => true, + 64844 => true, + 64845 => true, + 64846 => true, + 64847 => true, + 64912 => true, + 64913 => true, + 64968 => true, + 64969 => true, + 64970 => true, + 64971 => true, + 64972 => true, + 64973 => true, + 64974 => true, + 64975 => true, + 65022 => true, + 65023 => true, + 65042 => true, + 65049 => true, + 65050 => true, + 65051 => true, + 65052 => true, + 65053 => true, + 65054 => true, + 65055 => true, + 65072 => true, + 65106 => true, + 65107 => true, + 65127 => true, + 65132 => true, + 65133 => true, + 65134 => true, + 65135 => true, + 65141 => true, + 65277 => true, + 65278 => true, + 65280 => true, + 65440 => true, + 65471 => true, + 65472 => true, + 65473 => true, + 65480 => true, + 65481 => true, + 65488 => true, + 65489 => true, + 65496 => true, + 65497 => true, + 65501 => true, + 65502 => true, + 65503 => true, + 65511 => true, + 65519 => true, + 65520 => true, + 65521 => true, + 65522 => true, + 65523 => true, + 65524 => true, + 65525 => true, + 65526 => true, + 65527 => true, + 65528 => true, + 65529 => true, + 65530 => true, + 65531 => true, + 65532 => true, + 65533 => true, + 65534 => true, + 65535 => true, + 65548 => true, + 65575 => true, + 65595 => true, + 65598 => true, + 65614 => true, + 65615 => true, + 65787 => true, + 65788 => true, + 65789 => true, + 65790 => true, + 65791 => true, + 65795 => true, + 65796 => true, + 65797 => true, + 65798 => true, + 65844 => true, + 65845 => true, + 65846 => true, + 65935 => true, + 65949 => true, + 65950 => true, + 65951 => true, + 66205 => true, + 66206 => true, + 66207 => true, + 66257 => true, + 66258 => true, + 66259 => true, + 66260 => true, + 66261 => true, + 66262 => true, + 66263 => true, + 66264 => true, + 66265 => true, + 66266 => true, + 66267 => true, + 66268 => true, + 66269 => true, + 66270 => true, + 66271 => true, + 66300 => true, + 66301 => true, + 66302 => true, + 66303 => true, + 66340 => true, + 66341 => true, + 66342 => true, + 66343 => true, + 66344 => true, + 66345 => true, + 66346 => true, + 66347 => true, + 66348 => true, + 66379 => true, + 66380 => true, + 66381 => true, + 66382 => true, + 66383 => true, + 66427 => true, + 66428 => true, + 66429 => true, + 66430 => true, + 66431 => true, + 66462 => true, + 66500 => true, + 66501 => true, + 66502 => true, + 66503 => true, + 66718 => true, + 66719 => true, + 66730 => true, + 66731 => true, + 66732 => true, + 66733 => true, + 66734 => true, + 66735 => true, + 66772 => true, + 66773 => true, + 66774 => true, + 66775 => true, + 66812 => true, + 66813 => true, + 66814 => true, + 66815 => true, + 66856 => true, + 66857 => true, + 66858 => true, + 66859 => true, + 66860 => true, + 66861 => true, + 66862 => true, + 66863 => true, + 66916 => true, + 66917 => true, + 66918 => true, + 66919 => true, + 66920 => true, + 66921 => true, + 66922 => true, + 66923 => true, + 66924 => true, + 66925 => true, + 66926 => true, + 67383 => true, + 67384 => true, + 67385 => true, + 67386 => true, + 67387 => true, + 67388 => true, + 67389 => true, + 67390 => true, + 67391 => true, + 67414 => true, + 67415 => true, + 67416 => true, + 67417 => true, + 67418 => true, + 67419 => true, + 67420 => true, + 67421 => true, + 67422 => true, + 67423 => true, + 67590 => true, + 67591 => true, + 67593 => true, + 67638 => true, + 67641 => true, + 67642 => true, + 67643 => true, + 67645 => true, + 67646 => true, + 67670 => true, + 67743 => true, + 67744 => true, + 67745 => true, + 67746 => true, + 67747 => true, + 67748 => true, + 67749 => true, + 67750 => true, + 67827 => true, + 67830 => true, + 67831 => true, + 67832 => true, + 67833 => true, + 67834 => true, + 67868 => true, + 67869 => true, + 67870 => true, + 67898 => true, + 67899 => true, + 67900 => true, + 67901 => true, + 67902 => true, + 68024 => true, + 68025 => true, + 68026 => true, + 68027 => true, + 68048 => true, + 68049 => true, + 68100 => true, + 68103 => true, + 68104 => true, + 68105 => true, + 68106 => true, + 68107 => true, + 68116 => true, + 68120 => true, + 68150 => true, + 68151 => true, + 68155 => true, + 68156 => true, + 68157 => true, + 68158 => true, + 68169 => true, + 68170 => true, + 68171 => true, + 68172 => true, + 68173 => true, + 68174 => true, + 68175 => true, + 68185 => true, + 68186 => true, + 68187 => true, + 68188 => true, + 68189 => true, + 68190 => true, + 68191 => true, + 68327 => true, + 68328 => true, + 68329 => true, + 68330 => true, + 68343 => true, + 68344 => true, + 68345 => true, + 68346 => true, + 68347 => true, + 68348 => true, + 68349 => true, + 68350 => true, + 68351 => true, + 68406 => true, + 68407 => true, + 68408 => true, + 68438 => true, + 68439 => true, + 68467 => true, + 68468 => true, + 68469 => true, + 68470 => true, + 68471 => true, + 68498 => true, + 68499 => true, + 68500 => true, + 68501 => true, + 68502 => true, + 68503 => true, + 68504 => true, + 68509 => true, + 68510 => true, + 68511 => true, + 68512 => true, + 68513 => true, + 68514 => true, + 68515 => true, + 68516 => true, + 68517 => true, + 68518 => true, + 68519 => true, + 68520 => true, + 68787 => true, + 68788 => true, + 68789 => true, + 68790 => true, + 68791 => true, + 68792 => true, + 68793 => true, + 68794 => true, + 68795 => true, + 68796 => true, + 68797 => true, + 68798 => true, + 68799 => true, + 68851 => true, + 68852 => true, + 68853 => true, + 68854 => true, + 68855 => true, + 68856 => true, + 68857 => true, + 68904 => true, + 68905 => true, + 68906 => true, + 68907 => true, + 68908 => true, + 68909 => true, + 68910 => true, + 68911 => true, + 69247 => true, + 69290 => true, + 69294 => true, + 69295 => true, + 69416 => true, + 69417 => true, + 69418 => true, + 69419 => true, + 69420 => true, + 69421 => true, + 69422 => true, + 69423 => true, + 69580 => true, + 69581 => true, + 69582 => true, + 69583 => true, + 69584 => true, + 69585 => true, + 69586 => true, + 69587 => true, + 69588 => true, + 69589 => true, + 69590 => true, + 69591 => true, + 69592 => true, + 69593 => true, + 69594 => true, + 69595 => true, + 69596 => true, + 69597 => true, + 69598 => true, + 69599 => true, + 69623 => true, + 69624 => true, + 69625 => true, + 69626 => true, + 69627 => true, + 69628 => true, + 69629 => true, + 69630 => true, + 69631 => true, + 69710 => true, + 69711 => true, + 69712 => true, + 69713 => true, + 69744 => true, + 69745 => true, + 69746 => true, + 69747 => true, + 69748 => true, + 69749 => true, + 69750 => true, + 69751 => true, + 69752 => true, + 69753 => true, + 69754 => true, + 69755 => true, + 69756 => true, + 69757 => true, + 69758 => true, + 69821 => true, + 69826 => true, + 69827 => true, + 69828 => true, + 69829 => true, + 69830 => true, + 69831 => true, + 69832 => true, + 69833 => true, + 69834 => true, + 69835 => true, + 69836 => true, + 69837 => true, + 69838 => true, + 69839 => true, + 69865 => true, + 69866 => true, + 69867 => true, + 69868 => true, + 69869 => true, + 69870 => true, + 69871 => true, + 69882 => true, + 69883 => true, + 69884 => true, + 69885 => true, + 69886 => true, + 69887 => true, + 69941 => true, + 69960 => true, + 69961 => true, + 69962 => true, + 69963 => true, + 69964 => true, + 69965 => true, + 69966 => true, + 69967 => true, + 70007 => true, + 70008 => true, + 70009 => true, + 70010 => true, + 70011 => true, + 70012 => true, + 70013 => true, + 70014 => true, + 70015 => true, + 70112 => true, + 70133 => true, + 70134 => true, + 70135 => true, + 70136 => true, + 70137 => true, + 70138 => true, + 70139 => true, + 70140 => true, + 70141 => true, + 70142 => true, + 70143 => true, + 70162 => true, + 70279 => true, + 70281 => true, + 70286 => true, + 70302 => true, + 70314 => true, + 70315 => true, + 70316 => true, + 70317 => true, + 70318 => true, + 70319 => true, + 70379 => true, + 70380 => true, + 70381 => true, + 70382 => true, + 70383 => true, + 70394 => true, + 70395 => true, + 70396 => true, + 70397 => true, + 70398 => true, + 70399 => true, + 70404 => true, + 70413 => true, + 70414 => true, + 70417 => true, + 70418 => true, + 70441 => true, + 70449 => true, + 70452 => true, + 70458 => true, + 70469 => true, + 70470 => true, + 70473 => true, + 70474 => true, + 70478 => true, + 70479 => true, + 70481 => true, + 70482 => true, + 70483 => true, + 70484 => true, + 70485 => true, + 70486 => true, + 70488 => true, + 70489 => true, + 70490 => true, + 70491 => true, + 70492 => true, + 70500 => true, + 70501 => true, + 70509 => true, + 70510 => true, + 70511 => true, + 70748 => true, + 70754 => true, + 70755 => true, + 70756 => true, + 70757 => true, + 70758 => true, + 70759 => true, + 70760 => true, + 70761 => true, + 70762 => true, + 70763 => true, + 70764 => true, + 70765 => true, + 70766 => true, + 70767 => true, + 70768 => true, + 70769 => true, + 70770 => true, + 70771 => true, + 70772 => true, + 70773 => true, + 70774 => true, + 70775 => true, + 70776 => true, + 70777 => true, + 70778 => true, + 70779 => true, + 70780 => true, + 70781 => true, + 70782 => true, + 70783 => true, + 70856 => true, + 70857 => true, + 70858 => true, + 70859 => true, + 70860 => true, + 70861 => true, + 70862 => true, + 70863 => true, + 71094 => true, + 71095 => true, + 71237 => true, + 71238 => true, + 71239 => true, + 71240 => true, + 71241 => true, + 71242 => true, + 71243 => true, + 71244 => true, + 71245 => true, + 71246 => true, + 71247 => true, + 71258 => true, + 71259 => true, + 71260 => true, + 71261 => true, + 71262 => true, + 71263 => true, + 71277 => true, + 71278 => true, + 71279 => true, + 71280 => true, + 71281 => true, + 71282 => true, + 71283 => true, + 71284 => true, + 71285 => true, + 71286 => true, + 71287 => true, + 71288 => true, + 71289 => true, + 71290 => true, + 71291 => true, + 71292 => true, + 71293 => true, + 71294 => true, + 71295 => true, + 71353 => true, + 71354 => true, + 71355 => true, + 71356 => true, + 71357 => true, + 71358 => true, + 71359 => true, + 71451 => true, + 71452 => true, + 71468 => true, + 71469 => true, + 71470 => true, + 71471 => true, + 71923 => true, + 71924 => true, + 71925 => true, + 71926 => true, + 71927 => true, + 71928 => true, + 71929 => true, + 71930 => true, + 71931 => true, + 71932 => true, + 71933 => true, + 71934 => true, + 71943 => true, + 71944 => true, + 71946 => true, + 71947 => true, + 71956 => true, + 71959 => true, + 71990 => true, + 71993 => true, + 71994 => true, + 72007 => true, + 72008 => true, + 72009 => true, + 72010 => true, + 72011 => true, + 72012 => true, + 72013 => true, + 72014 => true, + 72015 => true, + 72104 => true, + 72105 => true, + 72152 => true, + 72153 => true, + 72165 => true, + 72166 => true, + 72167 => true, + 72168 => true, + 72169 => true, + 72170 => true, + 72171 => true, + 72172 => true, + 72173 => true, + 72174 => true, + 72175 => true, + 72176 => true, + 72177 => true, + 72178 => true, + 72179 => true, + 72180 => true, + 72181 => true, + 72182 => true, + 72183 => true, + 72184 => true, + 72185 => true, + 72186 => true, + 72187 => true, + 72188 => true, + 72189 => true, + 72190 => true, + 72191 => true, + 72264 => true, + 72265 => true, + 72266 => true, + 72267 => true, + 72268 => true, + 72269 => true, + 72270 => true, + 72271 => true, + 72355 => true, + 72356 => true, + 72357 => true, + 72358 => true, + 72359 => true, + 72360 => true, + 72361 => true, + 72362 => true, + 72363 => true, + 72364 => true, + 72365 => true, + 72366 => true, + 72367 => true, + 72368 => true, + 72369 => true, + 72370 => true, + 72371 => true, + 72372 => true, + 72373 => true, + 72374 => true, + 72375 => true, + 72376 => true, + 72377 => true, + 72378 => true, + 72379 => true, + 72380 => true, + 72381 => true, + 72382 => true, + 72383 => true, + 72713 => true, + 72759 => true, + 72774 => true, + 72775 => true, + 72776 => true, + 72777 => true, + 72778 => true, + 72779 => true, + 72780 => true, + 72781 => true, + 72782 => true, + 72783 => true, + 72813 => true, + 72814 => true, + 72815 => true, + 72848 => true, + 72849 => true, + 72872 => true, + 72967 => true, + 72970 => true, + 73015 => true, + 73016 => true, + 73017 => true, + 73019 => true, + 73022 => true, + 73032 => true, + 73033 => true, + 73034 => true, + 73035 => true, + 73036 => true, + 73037 => true, + 73038 => true, + 73039 => true, + 73050 => true, + 73051 => true, + 73052 => true, + 73053 => true, + 73054 => true, + 73055 => true, + 73062 => true, + 73065 => true, + 73103 => true, + 73106 => true, + 73113 => true, + 73114 => true, + 73115 => true, + 73116 => true, + 73117 => true, + 73118 => true, + 73119 => true, + 73649 => true, + 73650 => true, + 73651 => true, + 73652 => true, + 73653 => true, + 73654 => true, + 73655 => true, + 73656 => true, + 73657 => true, + 73658 => true, + 73659 => true, + 73660 => true, + 73661 => true, + 73662 => true, + 73663 => true, + 73714 => true, + 73715 => true, + 73716 => true, + 73717 => true, + 73718 => true, + 73719 => true, + 73720 => true, + 73721 => true, + 73722 => true, + 73723 => true, + 73724 => true, + 73725 => true, + 73726 => true, + 74863 => true, + 74869 => true, + 74870 => true, + 74871 => true, + 74872 => true, + 74873 => true, + 74874 => true, + 74875 => true, + 74876 => true, + 74877 => true, + 74878 => true, + 74879 => true, + 78895 => true, + 78896 => true, + 78897 => true, + 78898 => true, + 78899 => true, + 78900 => true, + 78901 => true, + 78902 => true, + 78903 => true, + 78904 => true, + 92729 => true, + 92730 => true, + 92731 => true, + 92732 => true, + 92733 => true, + 92734 => true, + 92735 => true, + 92767 => true, + 92778 => true, + 92779 => true, + 92780 => true, + 92781 => true, + 92910 => true, + 92911 => true, + 92918 => true, + 92919 => true, + 92920 => true, + 92921 => true, + 92922 => true, + 92923 => true, + 92924 => true, + 92925 => true, + 92926 => true, + 92927 => true, + 92998 => true, + 92999 => true, + 93000 => true, + 93001 => true, + 93002 => true, + 93003 => true, + 93004 => true, + 93005 => true, + 93006 => true, + 93007 => true, + 93018 => true, + 93026 => true, + 93048 => true, + 93049 => true, + 93050 => true, + 93051 => true, + 93052 => true, + 94027 => true, + 94028 => true, + 94029 => true, + 94030 => true, + 94088 => true, + 94089 => true, + 94090 => true, + 94091 => true, + 94092 => true, + 94093 => true, + 94094 => true, + 94181 => true, + 94182 => true, + 94183 => true, + 94184 => true, + 94185 => true, + 94186 => true, + 94187 => true, + 94188 => true, + 94189 => true, + 94190 => true, + 94191 => true, + 94194 => true, + 94195 => true, + 94196 => true, + 94197 => true, + 94198 => true, + 94199 => true, + 94200 => true, + 94201 => true, + 94202 => true, + 94203 => true, + 94204 => true, + 94205 => true, + 94206 => true, + 94207 => true, + 100344 => true, + 100345 => true, + 100346 => true, + 100347 => true, + 100348 => true, + 100349 => true, + 100350 => true, + 100351 => true, + 110931 => true, + 110932 => true, + 110933 => true, + 110934 => true, + 110935 => true, + 110936 => true, + 110937 => true, + 110938 => true, + 110939 => true, + 110940 => true, + 110941 => true, + 110942 => true, + 110943 => true, + 110944 => true, + 110945 => true, + 110946 => true, + 110947 => true, + 110952 => true, + 110953 => true, + 110954 => true, + 110955 => true, + 110956 => true, + 110957 => true, + 110958 => true, + 110959 => true, + 113771 => true, + 113772 => true, + 113773 => true, + 113774 => true, + 113775 => true, + 113789 => true, + 113790 => true, + 113791 => true, + 113801 => true, + 113802 => true, + 113803 => true, + 113804 => true, + 113805 => true, + 113806 => true, + 113807 => true, + 113818 => true, + 113819 => true, + 119030 => true, + 119031 => true, + 119032 => true, + 119033 => true, + 119034 => true, + 119035 => true, + 119036 => true, + 119037 => true, + 119038 => true, + 119039 => true, + 119079 => true, + 119080 => true, + 119155 => true, + 119156 => true, + 119157 => true, + 119158 => true, + 119159 => true, + 119160 => true, + 119161 => true, + 119162 => true, + 119273 => true, + 119274 => true, + 119275 => true, + 119276 => true, + 119277 => true, + 119278 => true, + 119279 => true, + 119280 => true, + 119281 => true, + 119282 => true, + 119283 => true, + 119284 => true, + 119285 => true, + 119286 => true, + 119287 => true, + 119288 => true, + 119289 => true, + 119290 => true, + 119291 => true, + 119292 => true, + 119293 => true, + 119294 => true, + 119295 => true, + 119540 => true, + 119541 => true, + 119542 => true, + 119543 => true, + 119544 => true, + 119545 => true, + 119546 => true, + 119547 => true, + 119548 => true, + 119549 => true, + 119550 => true, + 119551 => true, + 119639 => true, + 119640 => true, + 119641 => true, + 119642 => true, + 119643 => true, + 119644 => true, + 119645 => true, + 119646 => true, + 119647 => true, + 119893 => true, + 119965 => true, + 119968 => true, + 119969 => true, + 119971 => true, + 119972 => true, + 119975 => true, + 119976 => true, + 119981 => true, + 119994 => true, + 119996 => true, + 120004 => true, + 120070 => true, + 120075 => true, + 120076 => true, + 120085 => true, + 120093 => true, + 120122 => true, + 120127 => true, + 120133 => true, + 120135 => true, + 120136 => true, + 120137 => true, + 120145 => true, + 120486 => true, + 120487 => true, + 120780 => true, + 120781 => true, + 121484 => true, + 121485 => true, + 121486 => true, + 121487 => true, + 121488 => true, + 121489 => true, + 121490 => true, + 121491 => true, + 121492 => true, + 121493 => true, + 121494 => true, + 121495 => true, + 121496 => true, + 121497 => true, + 121498 => true, + 121504 => true, + 122887 => true, + 122905 => true, + 122906 => true, + 122914 => true, + 122917 => true, + 123181 => true, + 123182 => true, + 123183 => true, + 123198 => true, + 123199 => true, + 123210 => true, + 123211 => true, + 123212 => true, + 123213 => true, + 123642 => true, + 123643 => true, + 123644 => true, + 123645 => true, + 123646 => true, + 125125 => true, + 125126 => true, + 125260 => true, + 125261 => true, + 125262 => true, + 125263 => true, + 125274 => true, + 125275 => true, + 125276 => true, + 125277 => true, + 126468 => true, + 126496 => true, + 126499 => true, + 126501 => true, + 126502 => true, + 126504 => true, + 126515 => true, + 126520 => true, + 126522 => true, + 126524 => true, + 126525 => true, + 126526 => true, + 126527 => true, + 126528 => true, + 126529 => true, + 126531 => true, + 126532 => true, + 126533 => true, + 126534 => true, + 126536 => true, + 126538 => true, + 126540 => true, + 126544 => true, + 126547 => true, + 126549 => true, + 126550 => true, + 126552 => true, + 126554 => true, + 126556 => true, + 126558 => true, + 126560 => true, + 126563 => true, + 126565 => true, + 126566 => true, + 126571 => true, + 126579 => true, + 126584 => true, + 126589 => true, + 126591 => true, + 126602 => true, + 126620 => true, + 126621 => true, + 126622 => true, + 126623 => true, + 126624 => true, + 126628 => true, + 126634 => true, + 127020 => true, + 127021 => true, + 127022 => true, + 127023 => true, + 127124 => true, + 127125 => true, + 127126 => true, + 127127 => true, + 127128 => true, + 127129 => true, + 127130 => true, + 127131 => true, + 127132 => true, + 127133 => true, + 127134 => true, + 127135 => true, + 127151 => true, + 127152 => true, + 127168 => true, + 127184 => true, + 127222 => true, + 127223 => true, + 127224 => true, + 127225 => true, + 127226 => true, + 127227 => true, + 127228 => true, + 127229 => true, + 127230 => true, + 127231 => true, + 127232 => true, + 127491 => true, + 127492 => true, + 127493 => true, + 127494 => true, + 127495 => true, + 127496 => true, + 127497 => true, + 127498 => true, + 127499 => true, + 127500 => true, + 127501 => true, + 127502 => true, + 127503 => true, + 127548 => true, + 127549 => true, + 127550 => true, + 127551 => true, + 127561 => true, + 127562 => true, + 127563 => true, + 127564 => true, + 127565 => true, + 127566 => true, + 127567 => true, + 127570 => true, + 127571 => true, + 127572 => true, + 127573 => true, + 127574 => true, + 127575 => true, + 127576 => true, + 127577 => true, + 127578 => true, + 127579 => true, + 127580 => true, + 127581 => true, + 127582 => true, + 127583 => true, + 128728 => true, + 128729 => true, + 128730 => true, + 128731 => true, + 128732 => true, + 128733 => true, + 128734 => true, + 128735 => true, + 128749 => true, + 128750 => true, + 128751 => true, + 128765 => true, + 128766 => true, + 128767 => true, + 128884 => true, + 128885 => true, + 128886 => true, + 128887 => true, + 128888 => true, + 128889 => true, + 128890 => true, + 128891 => true, + 128892 => true, + 128893 => true, + 128894 => true, + 128895 => true, + 128985 => true, + 128986 => true, + 128987 => true, + 128988 => true, + 128989 => true, + 128990 => true, + 128991 => true, + 129004 => true, + 129005 => true, + 129006 => true, + 129007 => true, + 129008 => true, + 129009 => true, + 129010 => true, + 129011 => true, + 129012 => true, + 129013 => true, + 129014 => true, + 129015 => true, + 129016 => true, + 129017 => true, + 129018 => true, + 129019 => true, + 129020 => true, + 129021 => true, + 129022 => true, + 129023 => true, + 129036 => true, + 129037 => true, + 129038 => true, + 129039 => true, + 129096 => true, + 129097 => true, + 129098 => true, + 129099 => true, + 129100 => true, + 129101 => true, + 129102 => true, + 129103 => true, + 129114 => true, + 129115 => true, + 129116 => true, + 129117 => true, + 129118 => true, + 129119 => true, + 129160 => true, + 129161 => true, + 129162 => true, + 129163 => true, + 129164 => true, + 129165 => true, + 129166 => true, + 129167 => true, + 129198 => true, + 129199 => true, + 129401 => true, + 129484 => true, + 129620 => true, + 129621 => true, + 129622 => true, + 129623 => true, + 129624 => true, + 129625 => true, + 129626 => true, + 129627 => true, + 129628 => true, + 129629 => true, + 129630 => true, + 129631 => true, + 129646 => true, + 129647 => true, + 129653 => true, + 129654 => true, + 129655 => true, + 129659 => true, + 129660 => true, + 129661 => true, + 129662 => true, + 129663 => true, + 129671 => true, + 129672 => true, + 129673 => true, + 129674 => true, + 129675 => true, + 129676 => true, + 129677 => true, + 129678 => true, + 129679 => true, + 129705 => true, + 129706 => true, + 129707 => true, + 129708 => true, + 129709 => true, + 129710 => true, + 129711 => true, + 129719 => true, + 129720 => true, + 129721 => true, + 129722 => true, + 129723 => true, + 129724 => true, + 129725 => true, + 129726 => true, + 129727 => true, + 129731 => true, + 129732 => true, + 129733 => true, + 129734 => true, + 129735 => true, + 129736 => true, + 129737 => true, + 129738 => true, + 129739 => true, + 129740 => true, + 129741 => true, + 129742 => true, + 129743 => true, + 129939 => true, + 131070 => true, + 131071 => true, + 177973 => true, + 177974 => true, + 177975 => true, + 177976 => true, + 177977 => true, + 177978 => true, + 177979 => true, + 177980 => true, + 177981 => true, + 177982 => true, + 177983 => true, + 178206 => true, + 178207 => true, + 183970 => true, + 183971 => true, + 183972 => true, + 183973 => true, + 183974 => true, + 183975 => true, + 183976 => true, + 183977 => true, + 183978 => true, + 183979 => true, + 183980 => true, + 183981 => true, + 183982 => true, + 183983 => true, + 194664 => true, + 194676 => true, + 194847 => true, + 194911 => true, + 195007 => true, + 196606 => true, + 196607 => true, + 262142 => true, + 262143 => true, + 327678 => true, + 327679 => true, + 393214 => true, + 393215 => true, + 458750 => true, + 458751 => true, + 524286 => true, + 524287 => true, + 589822 => true, + 589823 => true, + 655358 => true, + 655359 => true, + 720894 => true, + 720895 => true, + 786430 => true, + 786431 => true, + 851966 => true, + 851967 => true, + 917502 => true, + 917503 => true, + 917504 => true, + 917505 => true, + 917506 => true, + 917507 => true, + 917508 => true, + 917509 => true, + 917510 => true, + 917511 => true, + 917512 => true, + 917513 => true, + 917514 => true, + 917515 => true, + 917516 => true, + 917517 => true, + 917518 => true, + 917519 => true, + 917520 => true, + 917521 => true, + 917522 => true, + 917523 => true, + 917524 => true, + 917525 => true, + 917526 => true, + 917527 => true, + 917528 => true, + 917529 => true, + 917530 => true, + 917531 => true, + 917532 => true, + 917533 => true, + 917534 => true, + 917535 => true, + 983038 => true, + 983039 => true, + 1048574 => true, + 1048575 => true, + 1114110 => true, + 1114111 => true, +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php new file mode 100644 index 0000000..54f21cc --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php @@ -0,0 +1,308 @@ + ' ', + 168 => ' ̈', + 175 => ' ̄', + 180 => ' ́', + 184 => ' ̧', + 728 => ' ̆', + 729 => ' ̇', + 730 => ' ̊', + 731 => ' ̨', + 732 => ' ̃', + 733 => ' ̋', + 890 => ' ι', + 894 => ';', + 900 => ' ́', + 901 => ' ̈́', + 8125 => ' ̓', + 8127 => ' ̓', + 8128 => ' ͂', + 8129 => ' ̈͂', + 8141 => ' ̓̀', + 8142 => ' ̓́', + 8143 => ' ̓͂', + 8157 => ' ̔̀', + 8158 => ' ̔́', + 8159 => ' ̔͂', + 8173 => ' ̈̀', + 8174 => ' ̈́', + 8175 => '`', + 8189 => ' ́', + 8190 => ' ̔', + 8192 => ' ', + 8193 => ' ', + 8194 => ' ', + 8195 => ' ', + 8196 => ' ', + 8197 => ' ', + 8198 => ' ', + 8199 => ' ', + 8200 => ' ', + 8201 => ' ', + 8202 => ' ', + 8215 => ' ̳', + 8239 => ' ', + 8252 => '!!', + 8254 => ' ̅', + 8263 => '??', + 8264 => '?!', + 8265 => '!?', + 8287 => ' ', + 8314 => '+', + 8316 => '=', + 8317 => '(', + 8318 => ')', + 8330 => '+', + 8332 => '=', + 8333 => '(', + 8334 => ')', + 8448 => 'a/c', + 8449 => 'a/s', + 8453 => 'c/o', + 8454 => 'c/u', + 9332 => '(1)', + 9333 => '(2)', + 9334 => '(3)', + 9335 => '(4)', + 9336 => '(5)', + 9337 => '(6)', + 9338 => '(7)', + 9339 => '(8)', + 9340 => '(9)', + 9341 => '(10)', + 9342 => '(11)', + 9343 => '(12)', + 9344 => '(13)', + 9345 => '(14)', + 9346 => '(15)', + 9347 => '(16)', + 9348 => '(17)', + 9349 => '(18)', + 9350 => '(19)', + 9351 => '(20)', + 9372 => '(a)', + 9373 => '(b)', + 9374 => '(c)', + 9375 => '(d)', + 9376 => '(e)', + 9377 => '(f)', + 9378 => '(g)', + 9379 => '(h)', + 9380 => '(i)', + 9381 => '(j)', + 9382 => '(k)', + 9383 => '(l)', + 9384 => '(m)', + 9385 => '(n)', + 9386 => '(o)', + 9387 => '(p)', + 9388 => '(q)', + 9389 => '(r)', + 9390 => '(s)', + 9391 => '(t)', + 9392 => '(u)', + 9393 => '(v)', + 9394 => '(w)', + 9395 => '(x)', + 9396 => '(y)', + 9397 => '(z)', + 10868 => '::=', + 10869 => '==', + 10870 => '===', + 12288 => ' ', + 12443 => ' ゙', + 12444 => ' ゚', + 12800 => '(ᄀ)', + 12801 => '(ᄂ)', + 12802 => '(ᄃ)', + 12803 => '(ᄅ)', + 12804 => '(ᄆ)', + 12805 => '(ᄇ)', + 12806 => '(ᄉ)', + 12807 => '(ᄋ)', + 12808 => '(ᄌ)', + 12809 => '(ᄎ)', + 12810 => '(ᄏ)', + 12811 => '(ᄐ)', + 12812 => '(ᄑ)', + 12813 => '(ᄒ)', + 12814 => '(가)', + 12815 => '(나)', + 12816 => '(다)', + 12817 => '(라)', + 12818 => '(마)', + 12819 => '(바)', + 12820 => '(사)', + 12821 => '(아)', + 12822 => '(자)', + 12823 => '(차)', + 12824 => '(카)', + 12825 => '(타)', + 12826 => '(파)', + 12827 => '(하)', + 12828 => '(주)', + 12829 => '(오전)', + 12830 => '(오후)', + 12832 => '(一)', + 12833 => '(二)', + 12834 => '(三)', + 12835 => '(四)', + 12836 => '(五)', + 12837 => '(六)', + 12838 => '(七)', + 12839 => '(八)', + 12840 => '(九)', + 12841 => '(十)', + 12842 => '(月)', + 12843 => '(火)', + 12844 => '(水)', + 12845 => '(木)', + 12846 => '(金)', + 12847 => '(土)', + 12848 => '(日)', + 12849 => '(株)', + 12850 => '(有)', + 12851 => '(社)', + 12852 => '(名)', + 12853 => '(特)', + 12854 => '(財)', + 12855 => '(祝)', + 12856 => '(労)', + 12857 => '(代)', + 12858 => '(呼)', + 12859 => '(学)', + 12860 => '(監)', + 12861 => '(企)', + 12862 => '(資)', + 12863 => '(協)', + 12864 => '(祭)', + 12865 => '(休)', + 12866 => '(自)', + 12867 => '(至)', + 64297 => '+', + 64606 => ' ٌّ', + 64607 => ' ٍّ', + 64608 => ' َّ', + 64609 => ' ُّ', + 64610 => ' ِّ', + 64611 => ' ّٰ', + 65018 => 'صلى الله عليه وسلم', + 65019 => 'جل جلاله', + 65040 => ',', + 65043 => ':', + 65044 => ';', + 65045 => '!', + 65046 => '?', + 65075 => '_', + 65076 => '_', + 65077 => '(', + 65078 => ')', + 65079 => '{', + 65080 => '}', + 65095 => '[', + 65096 => ']', + 65097 => ' ̅', + 65098 => ' ̅', + 65099 => ' ̅', + 65100 => ' ̅', + 65101 => '_', + 65102 => '_', + 65103 => '_', + 65104 => ',', + 65108 => ';', + 65109 => ':', + 65110 => '?', + 65111 => '!', + 65113 => '(', + 65114 => ')', + 65115 => '{', + 65116 => '}', + 65119 => '#', + 65120 => '&', + 65121 => '*', + 65122 => '+', + 65124 => '<', + 65125 => '>', + 65126 => '=', + 65128 => '\\', + 65129 => '$', + 65130 => '%', + 65131 => '@', + 65136 => ' ً', + 65138 => ' ٌ', + 65140 => ' ٍ', + 65142 => ' َ', + 65144 => ' ُ', + 65146 => ' ِ', + 65148 => ' ّ', + 65150 => ' ْ', + 65281 => '!', + 65282 => '"', + 65283 => '#', + 65284 => '$', + 65285 => '%', + 65286 => '&', + 65287 => '\'', + 65288 => '(', + 65289 => ')', + 65290 => '*', + 65291 => '+', + 65292 => ',', + 65295 => '/', + 65306 => ':', + 65307 => ';', + 65308 => '<', + 65309 => '=', + 65310 => '>', + 65311 => '?', + 65312 => '@', + 65339 => '[', + 65340 => '\\', + 65341 => ']', + 65342 => '^', + 65343 => '_', + 65344 => '`', + 65371 => '{', + 65372 => '|', + 65373 => '}', + 65374 => '~', + 65507 => ' ̄', + 127233 => '0,', + 127234 => '1,', + 127235 => '2,', + 127236 => '3,', + 127237 => '4,', + 127238 => '5,', + 127239 => '6,', + 127240 => '7,', + 127241 => '8,', + 127242 => '9,', + 127248 => '(a)', + 127249 => '(b)', + 127250 => '(c)', + 127251 => '(d)', + 127252 => '(e)', + 127253 => '(f)', + 127254 => '(g)', + 127255 => '(h)', + 127256 => '(i)', + 127257 => '(j)', + 127258 => '(k)', + 127259 => '(l)', + 127260 => '(m)', + 127261 => '(n)', + 127262 => '(o)', + 127263 => '(p)', + 127264 => '(q)', + 127265 => '(r)', + 127266 => '(s)', + 127267 => '(t)', + 127268 => '(u)', + 127269 => '(v)', + 127270 => '(w)', + 127271 => '(x)', + 127272 => '(y)', + 127273 => '(z)', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php new file mode 100644 index 0000000..223396e --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php @@ -0,0 +1,71 @@ + true, + 1 => true, + 2 => true, + 3 => true, + 4 => true, + 5 => true, + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + 11 => true, + 12 => true, + 13 => true, + 14 => true, + 15 => true, + 16 => true, + 17 => true, + 18 => true, + 19 => true, + 20 => true, + 21 => true, + 22 => true, + 23 => true, + 24 => true, + 25 => true, + 26 => true, + 27 => true, + 28 => true, + 29 => true, + 30 => true, + 31 => true, + 32 => true, + 33 => true, + 34 => true, + 35 => true, + 36 => true, + 37 => true, + 38 => true, + 39 => true, + 40 => true, + 41 => true, + 42 => true, + 43 => true, + 44 => true, + 47 => true, + 58 => true, + 59 => true, + 60 => true, + 61 => true, + 62 => true, + 63 => true, + 64 => true, + 91 => true, + 92 => true, + 93 => true, + 94 => true, + 95 => true, + 96 => true, + 123 => true, + 124 => true, + 125 => true, + 126 => true, + 127 => true, + 8800 => true, + 8814 => true, + 8815 => true, +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php new file mode 100644 index 0000000..b377844 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php @@ -0,0 +1,273 @@ + true, + 847 => true, + 6155 => true, + 6156 => true, + 6157 => true, + 8203 => true, + 8288 => true, + 8292 => true, + 65024 => true, + 65025 => true, + 65026 => true, + 65027 => true, + 65028 => true, + 65029 => true, + 65030 => true, + 65031 => true, + 65032 => true, + 65033 => true, + 65034 => true, + 65035 => true, + 65036 => true, + 65037 => true, + 65038 => true, + 65039 => true, + 65279 => true, + 113824 => true, + 113825 => true, + 113826 => true, + 113827 => true, + 917760 => true, + 917761 => true, + 917762 => true, + 917763 => true, + 917764 => true, + 917765 => true, + 917766 => true, + 917767 => true, + 917768 => true, + 917769 => true, + 917770 => true, + 917771 => true, + 917772 => true, + 917773 => true, + 917774 => true, + 917775 => true, + 917776 => true, + 917777 => true, + 917778 => true, + 917779 => true, + 917780 => true, + 917781 => true, + 917782 => true, + 917783 => true, + 917784 => true, + 917785 => true, + 917786 => true, + 917787 => true, + 917788 => true, + 917789 => true, + 917790 => true, + 917791 => true, + 917792 => true, + 917793 => true, + 917794 => true, + 917795 => true, + 917796 => true, + 917797 => true, + 917798 => true, + 917799 => true, + 917800 => true, + 917801 => true, + 917802 => true, + 917803 => true, + 917804 => true, + 917805 => true, + 917806 => true, + 917807 => true, + 917808 => true, + 917809 => true, + 917810 => true, + 917811 => true, + 917812 => true, + 917813 => true, + 917814 => true, + 917815 => true, + 917816 => true, + 917817 => true, + 917818 => true, + 917819 => true, + 917820 => true, + 917821 => true, + 917822 => true, + 917823 => true, + 917824 => true, + 917825 => true, + 917826 => true, + 917827 => true, + 917828 => true, + 917829 => true, + 917830 => true, + 917831 => true, + 917832 => true, + 917833 => true, + 917834 => true, + 917835 => true, + 917836 => true, + 917837 => true, + 917838 => true, + 917839 => true, + 917840 => true, + 917841 => true, + 917842 => true, + 917843 => true, + 917844 => true, + 917845 => true, + 917846 => true, + 917847 => true, + 917848 => true, + 917849 => true, + 917850 => true, + 917851 => true, + 917852 => true, + 917853 => true, + 917854 => true, + 917855 => true, + 917856 => true, + 917857 => true, + 917858 => true, + 917859 => true, + 917860 => true, + 917861 => true, + 917862 => true, + 917863 => true, + 917864 => true, + 917865 => true, + 917866 => true, + 917867 => true, + 917868 => true, + 917869 => true, + 917870 => true, + 917871 => true, + 917872 => true, + 917873 => true, + 917874 => true, + 917875 => true, + 917876 => true, + 917877 => true, + 917878 => true, + 917879 => true, + 917880 => true, + 917881 => true, + 917882 => true, + 917883 => true, + 917884 => true, + 917885 => true, + 917886 => true, + 917887 => true, + 917888 => true, + 917889 => true, + 917890 => true, + 917891 => true, + 917892 => true, + 917893 => true, + 917894 => true, + 917895 => true, + 917896 => true, + 917897 => true, + 917898 => true, + 917899 => true, + 917900 => true, + 917901 => true, + 917902 => true, + 917903 => true, + 917904 => true, + 917905 => true, + 917906 => true, + 917907 => true, + 917908 => true, + 917909 => true, + 917910 => true, + 917911 => true, + 917912 => true, + 917913 => true, + 917914 => true, + 917915 => true, + 917916 => true, + 917917 => true, + 917918 => true, + 917919 => true, + 917920 => true, + 917921 => true, + 917922 => true, + 917923 => true, + 917924 => true, + 917925 => true, + 917926 => true, + 917927 => true, + 917928 => true, + 917929 => true, + 917930 => true, + 917931 => true, + 917932 => true, + 917933 => true, + 917934 => true, + 917935 => true, + 917936 => true, + 917937 => true, + 917938 => true, + 917939 => true, + 917940 => true, + 917941 => true, + 917942 => true, + 917943 => true, + 917944 => true, + 917945 => true, + 917946 => true, + 917947 => true, + 917948 => true, + 917949 => true, + 917950 => true, + 917951 => true, + 917952 => true, + 917953 => true, + 917954 => true, + 917955 => true, + 917956 => true, + 917957 => true, + 917958 => true, + 917959 => true, + 917960 => true, + 917961 => true, + 917962 => true, + 917963 => true, + 917964 => true, + 917965 => true, + 917966 => true, + 917967 => true, + 917968 => true, + 917969 => true, + 917970 => true, + 917971 => true, + 917972 => true, + 917973 => true, + 917974 => true, + 917975 => true, + 917976 => true, + 917977 => true, + 917978 => true, + 917979 => true, + 917980 => true, + 917981 => true, + 917982 => true, + 917983 => true, + 917984 => true, + 917985 => true, + 917986 => true, + 917987 => true, + 917988 => true, + 917989 => true, + 917990 => true, + 917991 => true, + 917992 => true, + 917993 => true, + 917994 => true, + 917995 => true, + 917996 => true, + 917997 => true, + 917998 => true, + 917999 => true, +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php new file mode 100644 index 0000000..9b85fe9 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php @@ -0,0 +1,5778 @@ + 'a', + 66 => 'b', + 67 => 'c', + 68 => 'd', + 69 => 'e', + 70 => 'f', + 71 => 'g', + 72 => 'h', + 73 => 'i', + 74 => 'j', + 75 => 'k', + 76 => 'l', + 77 => 'm', + 78 => 'n', + 79 => 'o', + 80 => 'p', + 81 => 'q', + 82 => 'r', + 83 => 's', + 84 => 't', + 85 => 'u', + 86 => 'v', + 87 => 'w', + 88 => 'x', + 89 => 'y', + 90 => 'z', + 170 => 'a', + 178 => '2', + 179 => '3', + 181 => 'μ', + 185 => '1', + 186 => 'o', + 188 => '1⁄4', + 189 => '1⁄2', + 190 => '3⁄4', + 192 => 'à', + 193 => 'á', + 194 => 'â', + 195 => 'ã', + 196 => 'ä', + 197 => 'å', + 198 => 'æ', + 199 => 'ç', + 200 => 'è', + 201 => 'é', + 202 => 'ê', + 203 => 'ë', + 204 => 'ì', + 205 => 'í', + 206 => 'î', + 207 => 'ï', + 208 => 'ð', + 209 => 'ñ', + 210 => 'ò', + 211 => 'ó', + 212 => 'ô', + 213 => 'õ', + 214 => 'ö', + 216 => 'ø', + 217 => 'ù', + 218 => 'ú', + 219 => 'û', + 220 => 'ü', + 221 => 'ý', + 222 => 'þ', + 256 => 'ā', + 258 => 'ă', + 260 => 'ą', + 262 => 'ć', + 264 => 'ĉ', + 266 => 'ċ', + 268 => 'č', + 270 => 'ď', + 272 => 'đ', + 274 => 'ē', + 276 => 'ĕ', + 278 => 'ė', + 280 => 'ę', + 282 => 'ě', + 284 => 'ĝ', + 286 => 'ğ', + 288 => 'ġ', + 290 => 'ģ', + 292 => 'ĥ', + 294 => 'ħ', + 296 => 'ĩ', + 298 => 'ī', + 300 => 'ĭ', + 302 => 'į', + 304 => 'i̇', + 306 => 'ij', + 307 => 'ij', + 308 => 'ĵ', + 310 => 'ķ', + 313 => 'ĺ', + 315 => 'ļ', + 317 => 'ľ', + 319 => 'l·', + 320 => 'l·', + 321 => 'ł', + 323 => 'ń', + 325 => 'ņ', + 327 => 'ň', + 329 => 'ʼn', + 330 => 'ŋ', + 332 => 'ō', + 334 => 'ŏ', + 336 => 'ő', + 338 => 'œ', + 340 => 'ŕ', + 342 => 'ŗ', + 344 => 'ř', + 346 => 'ś', + 348 => 'ŝ', + 350 => 'ş', + 352 => 'š', + 354 => 'ţ', + 356 => 'ť', + 358 => 'ŧ', + 360 => 'ũ', + 362 => 'ū', + 364 => 'ŭ', + 366 => 'ů', + 368 => 'ű', + 370 => 'ų', + 372 => 'ŵ', + 374 => 'ŷ', + 376 => 'ÿ', + 377 => 'ź', + 379 => 'ż', + 381 => 'ž', + 383 => 's', + 385 => 'ɓ', + 386 => 'ƃ', + 388 => 'ƅ', + 390 => 'ɔ', + 391 => 'ƈ', + 393 => 'ɖ', + 394 => 'ɗ', + 395 => 'ƌ', + 398 => 'ǝ', + 399 => 'ə', + 400 => 'ɛ', + 401 => 'ƒ', + 403 => 'ɠ', + 404 => 'ɣ', + 406 => 'ɩ', + 407 => 'ɨ', + 408 => 'ƙ', + 412 => 'ɯ', + 413 => 'ɲ', + 415 => 'ɵ', + 416 => 'ơ', + 418 => 'ƣ', + 420 => 'ƥ', + 422 => 'ʀ', + 423 => 'ƨ', + 425 => 'ʃ', + 428 => 'ƭ', + 430 => 'ʈ', + 431 => 'ư', + 433 => 'ʊ', + 434 => 'ʋ', + 435 => 'ƴ', + 437 => 'ƶ', + 439 => 'ʒ', + 440 => 'ƹ', + 444 => 'ƽ', + 452 => 'dž', + 453 => 'dž', + 454 => 'dž', + 455 => 'lj', + 456 => 'lj', + 457 => 'lj', + 458 => 'nj', + 459 => 'nj', + 460 => 'nj', + 461 => 'ǎ', + 463 => 'ǐ', + 465 => 'ǒ', + 467 => 'ǔ', + 469 => 'ǖ', + 471 => 'ǘ', + 473 => 'ǚ', + 475 => 'ǜ', + 478 => 'ǟ', + 480 => 'ǡ', + 482 => 'ǣ', + 484 => 'ǥ', + 486 => 'ǧ', + 488 => 'ǩ', + 490 => 'ǫ', + 492 => 'ǭ', + 494 => 'ǯ', + 497 => 'dz', + 498 => 'dz', + 499 => 'dz', + 500 => 'ǵ', + 502 => 'ƕ', + 503 => 'ƿ', + 504 => 'ǹ', + 506 => 'ǻ', + 508 => 'ǽ', + 510 => 'ǿ', + 512 => 'ȁ', + 514 => 'ȃ', + 516 => 'ȅ', + 518 => 'ȇ', + 520 => 'ȉ', + 522 => 'ȋ', + 524 => 'ȍ', + 526 => 'ȏ', + 528 => 'ȑ', + 530 => 'ȓ', + 532 => 'ȕ', + 534 => 'ȗ', + 536 => 'ș', + 538 => 'ț', + 540 => 'ȝ', + 542 => 'ȟ', + 544 => 'ƞ', + 546 => 'ȣ', + 548 => 'ȥ', + 550 => 'ȧ', + 552 => 'ȩ', + 554 => 'ȫ', + 556 => 'ȭ', + 558 => 'ȯ', + 560 => 'ȱ', + 562 => 'ȳ', + 570 => 'ⱥ', + 571 => 'ȼ', + 573 => 'ƚ', + 574 => 'ⱦ', + 577 => 'ɂ', + 579 => 'ƀ', + 580 => 'ʉ', + 581 => 'ʌ', + 582 => 'ɇ', + 584 => 'ɉ', + 586 => 'ɋ', + 588 => 'ɍ', + 590 => 'ɏ', + 688 => 'h', + 689 => 'ɦ', + 690 => 'j', + 691 => 'r', + 692 => 'ɹ', + 693 => 'ɻ', + 694 => 'ʁ', + 695 => 'w', + 696 => 'y', + 736 => 'ɣ', + 737 => 'l', + 738 => 's', + 739 => 'x', + 740 => 'ʕ', + 832 => '̀', + 833 => '́', + 835 => '̓', + 836 => '̈́', + 837 => 'ι', + 880 => 'ͱ', + 882 => 'ͳ', + 884 => 'ʹ', + 886 => 'ͷ', + 895 => 'ϳ', + 902 => 'ά', + 903 => '·', + 904 => 'έ', + 905 => 'ή', + 906 => 'ί', + 908 => 'ό', + 910 => 'ύ', + 911 => 'ώ', + 913 => 'α', + 914 => 'β', + 915 => 'γ', + 916 => 'δ', + 917 => 'ε', + 918 => 'ζ', + 919 => 'η', + 920 => 'θ', + 921 => 'ι', + 922 => 'κ', + 923 => 'λ', + 924 => 'μ', + 925 => 'ν', + 926 => 'ξ', + 927 => 'ο', + 928 => 'π', + 929 => 'ρ', + 931 => 'σ', + 932 => 'τ', + 933 => 'υ', + 934 => 'φ', + 935 => 'χ', + 936 => 'ψ', + 937 => 'ω', + 938 => 'ϊ', + 939 => 'ϋ', + 975 => 'ϗ', + 976 => 'β', + 977 => 'θ', + 978 => 'υ', + 979 => 'ύ', + 980 => 'ϋ', + 981 => 'φ', + 982 => 'π', + 984 => 'ϙ', + 986 => 'ϛ', + 988 => 'ϝ', + 990 => 'ϟ', + 992 => 'ϡ', + 994 => 'ϣ', + 996 => 'ϥ', + 998 => 'ϧ', + 1000 => 'ϩ', + 1002 => 'ϫ', + 1004 => 'ϭ', + 1006 => 'ϯ', + 1008 => 'κ', + 1009 => 'ρ', + 1010 => 'σ', + 1012 => 'θ', + 1013 => 'ε', + 1015 => 'ϸ', + 1017 => 'σ', + 1018 => 'ϻ', + 1021 => 'ͻ', + 1022 => 'ͼ', + 1023 => 'ͽ', + 1024 => 'ѐ', + 1025 => 'ё', + 1026 => 'ђ', + 1027 => 'ѓ', + 1028 => 'є', + 1029 => 'ѕ', + 1030 => 'і', + 1031 => 'ї', + 1032 => 'ј', + 1033 => 'љ', + 1034 => 'њ', + 1035 => 'ћ', + 1036 => 'ќ', + 1037 => 'ѝ', + 1038 => 'ў', + 1039 => 'џ', + 1040 => 'а', + 1041 => 'б', + 1042 => 'в', + 1043 => 'г', + 1044 => 'д', + 1045 => 'е', + 1046 => 'ж', + 1047 => 'з', + 1048 => 'и', + 1049 => 'й', + 1050 => 'к', + 1051 => 'л', + 1052 => 'м', + 1053 => 'н', + 1054 => 'о', + 1055 => 'п', + 1056 => 'р', + 1057 => 'с', + 1058 => 'т', + 1059 => 'у', + 1060 => 'ф', + 1061 => 'х', + 1062 => 'ц', + 1063 => 'ч', + 1064 => 'ш', + 1065 => 'щ', + 1066 => 'ъ', + 1067 => 'ы', + 1068 => 'ь', + 1069 => 'э', + 1070 => 'ю', + 1071 => 'я', + 1120 => 'ѡ', + 1122 => 'ѣ', + 1124 => 'ѥ', + 1126 => 'ѧ', + 1128 => 'ѩ', + 1130 => 'ѫ', + 1132 => 'ѭ', + 1134 => 'ѯ', + 1136 => 'ѱ', + 1138 => 'ѳ', + 1140 => 'ѵ', + 1142 => 'ѷ', + 1144 => 'ѹ', + 1146 => 'ѻ', + 1148 => 'ѽ', + 1150 => 'ѿ', + 1152 => 'ҁ', + 1162 => 'ҋ', + 1164 => 'ҍ', + 1166 => 'ҏ', + 1168 => 'ґ', + 1170 => 'ғ', + 1172 => 'ҕ', + 1174 => 'җ', + 1176 => 'ҙ', + 1178 => 'қ', + 1180 => 'ҝ', + 1182 => 'ҟ', + 1184 => 'ҡ', + 1186 => 'ң', + 1188 => 'ҥ', + 1190 => 'ҧ', + 1192 => 'ҩ', + 1194 => 'ҫ', + 1196 => 'ҭ', + 1198 => 'ү', + 1200 => 'ұ', + 1202 => 'ҳ', + 1204 => 'ҵ', + 1206 => 'ҷ', + 1208 => 'ҹ', + 1210 => 'һ', + 1212 => 'ҽ', + 1214 => 'ҿ', + 1217 => 'ӂ', + 1219 => 'ӄ', + 1221 => 'ӆ', + 1223 => 'ӈ', + 1225 => 'ӊ', + 1227 => 'ӌ', + 1229 => 'ӎ', + 1232 => 'ӑ', + 1234 => 'ӓ', + 1236 => 'ӕ', + 1238 => 'ӗ', + 1240 => 'ә', + 1242 => 'ӛ', + 1244 => 'ӝ', + 1246 => 'ӟ', + 1248 => 'ӡ', + 1250 => 'ӣ', + 1252 => 'ӥ', + 1254 => 'ӧ', + 1256 => 'ө', + 1258 => 'ӫ', + 1260 => 'ӭ', + 1262 => 'ӯ', + 1264 => 'ӱ', + 1266 => 'ӳ', + 1268 => 'ӵ', + 1270 => 'ӷ', + 1272 => 'ӹ', + 1274 => 'ӻ', + 1276 => 'ӽ', + 1278 => 'ӿ', + 1280 => 'ԁ', + 1282 => 'ԃ', + 1284 => 'ԅ', + 1286 => 'ԇ', + 1288 => 'ԉ', + 1290 => 'ԋ', + 1292 => 'ԍ', + 1294 => 'ԏ', + 1296 => 'ԑ', + 1298 => 'ԓ', + 1300 => 'ԕ', + 1302 => 'ԗ', + 1304 => 'ԙ', + 1306 => 'ԛ', + 1308 => 'ԝ', + 1310 => 'ԟ', + 1312 => 'ԡ', + 1314 => 'ԣ', + 1316 => 'ԥ', + 1318 => 'ԧ', + 1320 => 'ԩ', + 1322 => 'ԫ', + 1324 => 'ԭ', + 1326 => 'ԯ', + 1329 => 'ա', + 1330 => 'բ', + 1331 => 'գ', + 1332 => 'դ', + 1333 => 'ե', + 1334 => 'զ', + 1335 => 'է', + 1336 => 'ը', + 1337 => 'թ', + 1338 => 'ժ', + 1339 => 'ի', + 1340 => 'լ', + 1341 => 'խ', + 1342 => 'ծ', + 1343 => 'կ', + 1344 => 'հ', + 1345 => 'ձ', + 1346 => 'ղ', + 1347 => 'ճ', + 1348 => 'մ', + 1349 => 'յ', + 1350 => 'ն', + 1351 => 'շ', + 1352 => 'ո', + 1353 => 'չ', + 1354 => 'պ', + 1355 => 'ջ', + 1356 => 'ռ', + 1357 => 'ս', + 1358 => 'վ', + 1359 => 'տ', + 1360 => 'ր', + 1361 => 'ց', + 1362 => 'ւ', + 1363 => 'փ', + 1364 => 'ք', + 1365 => 'օ', + 1366 => 'ֆ', + 1415 => 'եւ', + 1653 => 'اٴ', + 1654 => 'وٴ', + 1655 => 'ۇٴ', + 1656 => 'يٴ', + 2392 => 'क़', + 2393 => 'ख़', + 2394 => 'ग़', + 2395 => 'ज़', + 2396 => 'ड़', + 2397 => 'ढ़', + 2398 => 'फ़', + 2399 => 'य़', + 2524 => 'ড়', + 2525 => 'ঢ়', + 2527 => 'য়', + 2611 => 'ਲ਼', + 2614 => 'ਸ਼', + 2649 => 'ਖ਼', + 2650 => 'ਗ਼', + 2651 => 'ਜ਼', + 2654 => 'ਫ਼', + 2908 => 'ଡ଼', + 2909 => 'ଢ଼', + 3635 => 'ํา', + 3763 => 'ໍາ', + 3804 => 'ຫນ', + 3805 => 'ຫມ', + 3852 => '་', + 3907 => 'གྷ', + 3917 => 'ཌྷ', + 3922 => 'དྷ', + 3927 => 'བྷ', + 3932 => 'ཛྷ', + 3945 => 'ཀྵ', + 3955 => 'ཱི', + 3957 => 'ཱུ', + 3958 => 'ྲྀ', + 3959 => 'ྲཱྀ', + 3960 => 'ླྀ', + 3961 => 'ླཱྀ', + 3969 => 'ཱྀ', + 3987 => 'ྒྷ', + 3997 => 'ྜྷ', + 4002 => 'ྡྷ', + 4007 => 'ྦྷ', + 4012 => 'ྫྷ', + 4025 => 'ྐྵ', + 4295 => 'ⴧ', + 4301 => 'ⴭ', + 4348 => 'ნ', + 5112 => 'Ᏸ', + 5113 => 'Ᏹ', + 5114 => 'Ᏺ', + 5115 => 'Ᏻ', + 5116 => 'Ᏼ', + 5117 => 'Ᏽ', + 7296 => 'в', + 7297 => 'д', + 7298 => 'о', + 7299 => 'с', + 7300 => 'т', + 7301 => 'т', + 7302 => 'ъ', + 7303 => 'ѣ', + 7304 => 'ꙋ', + 7312 => 'ა', + 7313 => 'ბ', + 7314 => 'გ', + 7315 => 'დ', + 7316 => 'ე', + 7317 => 'ვ', + 7318 => 'ზ', + 7319 => 'თ', + 7320 => 'ი', + 7321 => 'კ', + 7322 => 'ლ', + 7323 => 'მ', + 7324 => 'ნ', + 7325 => 'ო', + 7326 => 'პ', + 7327 => 'ჟ', + 7328 => 'რ', + 7329 => 'ს', + 7330 => 'ტ', + 7331 => 'უ', + 7332 => 'ფ', + 7333 => 'ქ', + 7334 => 'ღ', + 7335 => 'ყ', + 7336 => 'შ', + 7337 => 'ჩ', + 7338 => 'ც', + 7339 => 'ძ', + 7340 => 'წ', + 7341 => 'ჭ', + 7342 => 'ხ', + 7343 => 'ჯ', + 7344 => 'ჰ', + 7345 => 'ჱ', + 7346 => 'ჲ', + 7347 => 'ჳ', + 7348 => 'ჴ', + 7349 => 'ჵ', + 7350 => 'ჶ', + 7351 => 'ჷ', + 7352 => 'ჸ', + 7353 => 'ჹ', + 7354 => 'ჺ', + 7357 => 'ჽ', + 7358 => 'ჾ', + 7359 => 'ჿ', + 7468 => 'a', + 7469 => 'æ', + 7470 => 'b', + 7472 => 'd', + 7473 => 'e', + 7474 => 'ǝ', + 7475 => 'g', + 7476 => 'h', + 7477 => 'i', + 7478 => 'j', + 7479 => 'k', + 7480 => 'l', + 7481 => 'm', + 7482 => 'n', + 7484 => 'o', + 7485 => 'ȣ', + 7486 => 'p', + 7487 => 'r', + 7488 => 't', + 7489 => 'u', + 7490 => 'w', + 7491 => 'a', + 7492 => 'ɐ', + 7493 => 'ɑ', + 7494 => 'ᴂ', + 7495 => 'b', + 7496 => 'd', + 7497 => 'e', + 7498 => 'ə', + 7499 => 'ɛ', + 7500 => 'ɜ', + 7501 => 'g', + 7503 => 'k', + 7504 => 'm', + 7505 => 'ŋ', + 7506 => 'o', + 7507 => 'ɔ', + 7508 => 'ᴖ', + 7509 => 'ᴗ', + 7510 => 'p', + 7511 => 't', + 7512 => 'u', + 7513 => 'ᴝ', + 7514 => 'ɯ', + 7515 => 'v', + 7516 => 'ᴥ', + 7517 => 'β', + 7518 => 'γ', + 7519 => 'δ', + 7520 => 'φ', + 7521 => 'χ', + 7522 => 'i', + 7523 => 'r', + 7524 => 'u', + 7525 => 'v', + 7526 => 'β', + 7527 => 'γ', + 7528 => 'ρ', + 7529 => 'φ', + 7530 => 'χ', + 7544 => 'н', + 7579 => 'ɒ', + 7580 => 'c', + 7581 => 'ɕ', + 7582 => 'ð', + 7583 => 'ɜ', + 7584 => 'f', + 7585 => 'ɟ', + 7586 => 'ɡ', + 7587 => 'ɥ', + 7588 => 'ɨ', + 7589 => 'ɩ', + 7590 => 'ɪ', + 7591 => 'ᵻ', + 7592 => 'ʝ', + 7593 => 'ɭ', + 7594 => 'ᶅ', + 7595 => 'ʟ', + 7596 => 'ɱ', + 7597 => 'ɰ', + 7598 => 'ɲ', + 7599 => 'ɳ', + 7600 => 'ɴ', + 7601 => 'ɵ', + 7602 => 'ɸ', + 7603 => 'ʂ', + 7604 => 'ʃ', + 7605 => 'ƫ', + 7606 => 'ʉ', + 7607 => 'ʊ', + 7608 => 'ᴜ', + 7609 => 'ʋ', + 7610 => 'ʌ', + 7611 => 'z', + 7612 => 'ʐ', + 7613 => 'ʑ', + 7614 => 'ʒ', + 7615 => 'θ', + 7680 => 'ḁ', + 7682 => 'ḃ', + 7684 => 'ḅ', + 7686 => 'ḇ', + 7688 => 'ḉ', + 7690 => 'ḋ', + 7692 => 'ḍ', + 7694 => 'ḏ', + 7696 => 'ḑ', + 7698 => 'ḓ', + 7700 => 'ḕ', + 7702 => 'ḗ', + 7704 => 'ḙ', + 7706 => 'ḛ', + 7708 => 'ḝ', + 7710 => 'ḟ', + 7712 => 'ḡ', + 7714 => 'ḣ', + 7716 => 'ḥ', + 7718 => 'ḧ', + 7720 => 'ḩ', + 7722 => 'ḫ', + 7724 => 'ḭ', + 7726 => 'ḯ', + 7728 => 'ḱ', + 7730 => 'ḳ', + 7732 => 'ḵ', + 7734 => 'ḷ', + 7736 => 'ḹ', + 7738 => 'ḻ', + 7740 => 'ḽ', + 7742 => 'ḿ', + 7744 => 'ṁ', + 7746 => 'ṃ', + 7748 => 'ṅ', + 7750 => 'ṇ', + 7752 => 'ṉ', + 7754 => 'ṋ', + 7756 => 'ṍ', + 7758 => 'ṏ', + 7760 => 'ṑ', + 7762 => 'ṓ', + 7764 => 'ṕ', + 7766 => 'ṗ', + 7768 => 'ṙ', + 7770 => 'ṛ', + 7772 => 'ṝ', + 7774 => 'ṟ', + 7776 => 'ṡ', + 7778 => 'ṣ', + 7780 => 'ṥ', + 7782 => 'ṧ', + 7784 => 'ṩ', + 7786 => 'ṫ', + 7788 => 'ṭ', + 7790 => 'ṯ', + 7792 => 'ṱ', + 7794 => 'ṳ', + 7796 => 'ṵ', + 7798 => 'ṷ', + 7800 => 'ṹ', + 7802 => 'ṻ', + 7804 => 'ṽ', + 7806 => 'ṿ', + 7808 => 'ẁ', + 7810 => 'ẃ', + 7812 => 'ẅ', + 7814 => 'ẇ', + 7816 => 'ẉ', + 7818 => 'ẋ', + 7820 => 'ẍ', + 7822 => 'ẏ', + 7824 => 'ẑ', + 7826 => 'ẓ', + 7828 => 'ẕ', + 7834 => 'aʾ', + 7835 => 'ṡ', + 7838 => 'ss', + 7840 => 'ạ', + 7842 => 'ả', + 7844 => 'ấ', + 7846 => 'ầ', + 7848 => 'ẩ', + 7850 => 'ẫ', + 7852 => 'ậ', + 7854 => 'ắ', + 7856 => 'ằ', + 7858 => 'ẳ', + 7860 => 'ẵ', + 7862 => 'ặ', + 7864 => 'ẹ', + 7866 => 'ẻ', + 7868 => 'ẽ', + 7870 => 'ế', + 7872 => 'ề', + 7874 => 'ể', + 7876 => 'ễ', + 7878 => 'ệ', + 7880 => 'ỉ', + 7882 => 'ị', + 7884 => 'ọ', + 7886 => 'ỏ', + 7888 => 'ố', + 7890 => 'ồ', + 7892 => 'ổ', + 7894 => 'ỗ', + 7896 => 'ộ', + 7898 => 'ớ', + 7900 => 'ờ', + 7902 => 'ở', + 7904 => 'ỡ', + 7906 => 'ợ', + 7908 => 'ụ', + 7910 => 'ủ', + 7912 => 'ứ', + 7914 => 'ừ', + 7916 => 'ử', + 7918 => 'ữ', + 7920 => 'ự', + 7922 => 'ỳ', + 7924 => 'ỵ', + 7926 => 'ỷ', + 7928 => 'ỹ', + 7930 => 'ỻ', + 7932 => 'ỽ', + 7934 => 'ỿ', + 7944 => 'ἀ', + 7945 => 'ἁ', + 7946 => 'ἂ', + 7947 => 'ἃ', + 7948 => 'ἄ', + 7949 => 'ἅ', + 7950 => 'ἆ', + 7951 => 'ἇ', + 7960 => 'ἐ', + 7961 => 'ἑ', + 7962 => 'ἒ', + 7963 => 'ἓ', + 7964 => 'ἔ', + 7965 => 'ἕ', + 7976 => 'ἠ', + 7977 => 'ἡ', + 7978 => 'ἢ', + 7979 => 'ἣ', + 7980 => 'ἤ', + 7981 => 'ἥ', + 7982 => 'ἦ', + 7983 => 'ἧ', + 7992 => 'ἰ', + 7993 => 'ἱ', + 7994 => 'ἲ', + 7995 => 'ἳ', + 7996 => 'ἴ', + 7997 => 'ἵ', + 7998 => 'ἶ', + 7999 => 'ἷ', + 8008 => 'ὀ', + 8009 => 'ὁ', + 8010 => 'ὂ', + 8011 => 'ὃ', + 8012 => 'ὄ', + 8013 => 'ὅ', + 8025 => 'ὑ', + 8027 => 'ὓ', + 8029 => 'ὕ', + 8031 => 'ὗ', + 8040 => 'ὠ', + 8041 => 'ὡ', + 8042 => 'ὢ', + 8043 => 'ὣ', + 8044 => 'ὤ', + 8045 => 'ὥ', + 8046 => 'ὦ', + 8047 => 'ὧ', + 8049 => 'ά', + 8051 => 'έ', + 8053 => 'ή', + 8055 => 'ί', + 8057 => 'ό', + 8059 => 'ύ', + 8061 => 'ώ', + 8064 => 'ἀι', + 8065 => 'ἁι', + 8066 => 'ἂι', + 8067 => 'ἃι', + 8068 => 'ἄι', + 8069 => 'ἅι', + 8070 => 'ἆι', + 8071 => 'ἇι', + 8072 => 'ἀι', + 8073 => 'ἁι', + 8074 => 'ἂι', + 8075 => 'ἃι', + 8076 => 'ἄι', + 8077 => 'ἅι', + 8078 => 'ἆι', + 8079 => 'ἇι', + 8080 => 'ἠι', + 8081 => 'ἡι', + 8082 => 'ἢι', + 8083 => 'ἣι', + 8084 => 'ἤι', + 8085 => 'ἥι', + 8086 => 'ἦι', + 8087 => 'ἧι', + 8088 => 'ἠι', + 8089 => 'ἡι', + 8090 => 'ἢι', + 8091 => 'ἣι', + 8092 => 'ἤι', + 8093 => 'ἥι', + 8094 => 'ἦι', + 8095 => 'ἧι', + 8096 => 'ὠι', + 8097 => 'ὡι', + 8098 => 'ὢι', + 8099 => 'ὣι', + 8100 => 'ὤι', + 8101 => 'ὥι', + 8102 => 'ὦι', + 8103 => 'ὧι', + 8104 => 'ὠι', + 8105 => 'ὡι', + 8106 => 'ὢι', + 8107 => 'ὣι', + 8108 => 'ὤι', + 8109 => 'ὥι', + 8110 => 'ὦι', + 8111 => 'ὧι', + 8114 => 'ὰι', + 8115 => 'αι', + 8116 => 'άι', + 8119 => 'ᾶι', + 8120 => 'ᾰ', + 8121 => 'ᾱ', + 8122 => 'ὰ', + 8123 => 'ά', + 8124 => 'αι', + 8126 => 'ι', + 8130 => 'ὴι', + 8131 => 'ηι', + 8132 => 'ήι', + 8135 => 'ῆι', + 8136 => 'ὲ', + 8137 => 'έ', + 8138 => 'ὴ', + 8139 => 'ή', + 8140 => 'ηι', + 8147 => 'ΐ', + 8152 => 'ῐ', + 8153 => 'ῑ', + 8154 => 'ὶ', + 8155 => 'ί', + 8163 => 'ΰ', + 8168 => 'ῠ', + 8169 => 'ῡ', + 8170 => 'ὺ', + 8171 => 'ύ', + 8172 => 'ῥ', + 8178 => 'ὼι', + 8179 => 'ωι', + 8180 => 'ώι', + 8183 => 'ῶι', + 8184 => 'ὸ', + 8185 => 'ό', + 8186 => 'ὼ', + 8187 => 'ώ', + 8188 => 'ωι', + 8209 => '‐', + 8243 => '′′', + 8244 => '′′′', + 8246 => '‵‵', + 8247 => '‵‵‵', + 8279 => '′′′′', + 8304 => '0', + 8305 => 'i', + 8308 => '4', + 8309 => '5', + 8310 => '6', + 8311 => '7', + 8312 => '8', + 8313 => '9', + 8315 => '−', + 8319 => 'n', + 8320 => '0', + 8321 => '1', + 8322 => '2', + 8323 => '3', + 8324 => '4', + 8325 => '5', + 8326 => '6', + 8327 => '7', + 8328 => '8', + 8329 => '9', + 8331 => '−', + 8336 => 'a', + 8337 => 'e', + 8338 => 'o', + 8339 => 'x', + 8340 => 'ə', + 8341 => 'h', + 8342 => 'k', + 8343 => 'l', + 8344 => 'm', + 8345 => 'n', + 8346 => 'p', + 8347 => 's', + 8348 => 't', + 8360 => 'rs', + 8450 => 'c', + 8451 => '°c', + 8455 => 'ɛ', + 8457 => '°f', + 8458 => 'g', + 8459 => 'h', + 8460 => 'h', + 8461 => 'h', + 8462 => 'h', + 8463 => 'ħ', + 8464 => 'i', + 8465 => 'i', + 8466 => 'l', + 8467 => 'l', + 8469 => 'n', + 8470 => 'no', + 8473 => 'p', + 8474 => 'q', + 8475 => 'r', + 8476 => 'r', + 8477 => 'r', + 8480 => 'sm', + 8481 => 'tel', + 8482 => 'tm', + 8484 => 'z', + 8486 => 'ω', + 8488 => 'z', + 8490 => 'k', + 8491 => 'å', + 8492 => 'b', + 8493 => 'c', + 8495 => 'e', + 8496 => 'e', + 8497 => 'f', + 8499 => 'm', + 8500 => 'o', + 8501 => 'א', + 8502 => 'ב', + 8503 => 'ג', + 8504 => 'ד', + 8505 => 'i', + 8507 => 'fax', + 8508 => 'π', + 8509 => 'γ', + 8510 => 'γ', + 8511 => 'π', + 8512 => '∑', + 8517 => 'd', + 8518 => 'd', + 8519 => 'e', + 8520 => 'i', + 8521 => 'j', + 8528 => '1⁄7', + 8529 => '1⁄9', + 8530 => '1⁄10', + 8531 => '1⁄3', + 8532 => '2⁄3', + 8533 => '1⁄5', + 8534 => '2⁄5', + 8535 => '3⁄5', + 8536 => '4⁄5', + 8537 => '1⁄6', + 8538 => '5⁄6', + 8539 => '1⁄8', + 8540 => '3⁄8', + 8541 => '5⁄8', + 8542 => '7⁄8', + 8543 => '1⁄', + 8544 => 'i', + 8545 => 'ii', + 8546 => 'iii', + 8547 => 'iv', + 8548 => 'v', + 8549 => 'vi', + 8550 => 'vii', + 8551 => 'viii', + 8552 => 'ix', + 8553 => 'x', + 8554 => 'xi', + 8555 => 'xii', + 8556 => 'l', + 8557 => 'c', + 8558 => 'd', + 8559 => 'm', + 8560 => 'i', + 8561 => 'ii', + 8562 => 'iii', + 8563 => 'iv', + 8564 => 'v', + 8565 => 'vi', + 8566 => 'vii', + 8567 => 'viii', + 8568 => 'ix', + 8569 => 'x', + 8570 => 'xi', + 8571 => 'xii', + 8572 => 'l', + 8573 => 'c', + 8574 => 'd', + 8575 => 'm', + 8585 => '0⁄3', + 8748 => '∫∫', + 8749 => '∫∫∫', + 8751 => '∮∮', + 8752 => '∮∮∮', + 9001 => '〈', + 9002 => '〉', + 9312 => '1', + 9313 => '2', + 9314 => '3', + 9315 => '4', + 9316 => '5', + 9317 => '6', + 9318 => '7', + 9319 => '8', + 9320 => '9', + 9321 => '10', + 9322 => '11', + 9323 => '12', + 9324 => '13', + 9325 => '14', + 9326 => '15', + 9327 => '16', + 9328 => '17', + 9329 => '18', + 9330 => '19', + 9331 => '20', + 9398 => 'a', + 9399 => 'b', + 9400 => 'c', + 9401 => 'd', + 9402 => 'e', + 9403 => 'f', + 9404 => 'g', + 9405 => 'h', + 9406 => 'i', + 9407 => 'j', + 9408 => 'k', + 9409 => 'l', + 9410 => 'm', + 9411 => 'n', + 9412 => 'o', + 9413 => 'p', + 9414 => 'q', + 9415 => 'r', + 9416 => 's', + 9417 => 't', + 9418 => 'u', + 9419 => 'v', + 9420 => 'w', + 9421 => 'x', + 9422 => 'y', + 9423 => 'z', + 9424 => 'a', + 9425 => 'b', + 9426 => 'c', + 9427 => 'd', + 9428 => 'e', + 9429 => 'f', + 9430 => 'g', + 9431 => 'h', + 9432 => 'i', + 9433 => 'j', + 9434 => 'k', + 9435 => 'l', + 9436 => 'm', + 9437 => 'n', + 9438 => 'o', + 9439 => 'p', + 9440 => 'q', + 9441 => 'r', + 9442 => 's', + 9443 => 't', + 9444 => 'u', + 9445 => 'v', + 9446 => 'w', + 9447 => 'x', + 9448 => 'y', + 9449 => 'z', + 9450 => '0', + 10764 => '∫∫∫∫', + 10972 => '⫝̸', + 11264 => 'ⰰ', + 11265 => 'ⰱ', + 11266 => 'ⰲ', + 11267 => 'ⰳ', + 11268 => 'ⰴ', + 11269 => 'ⰵ', + 11270 => 'ⰶ', + 11271 => 'ⰷ', + 11272 => 'ⰸ', + 11273 => 'ⰹ', + 11274 => 'ⰺ', + 11275 => 'ⰻ', + 11276 => 'ⰼ', + 11277 => 'ⰽ', + 11278 => 'ⰾ', + 11279 => 'ⰿ', + 11280 => 'ⱀ', + 11281 => 'ⱁ', + 11282 => 'ⱂ', + 11283 => 'ⱃ', + 11284 => 'ⱄ', + 11285 => 'ⱅ', + 11286 => 'ⱆ', + 11287 => 'ⱇ', + 11288 => 'ⱈ', + 11289 => 'ⱉ', + 11290 => 'ⱊ', + 11291 => 'ⱋ', + 11292 => 'ⱌ', + 11293 => 'ⱍ', + 11294 => 'ⱎ', + 11295 => 'ⱏ', + 11296 => 'ⱐ', + 11297 => 'ⱑ', + 11298 => 'ⱒ', + 11299 => 'ⱓ', + 11300 => 'ⱔ', + 11301 => 'ⱕ', + 11302 => 'ⱖ', + 11303 => 'ⱗ', + 11304 => 'ⱘ', + 11305 => 'ⱙ', + 11306 => 'ⱚ', + 11307 => 'ⱛ', + 11308 => 'ⱜ', + 11309 => 'ⱝ', + 11310 => 'ⱞ', + 11360 => 'ⱡ', + 11362 => 'ɫ', + 11363 => 'ᵽ', + 11364 => 'ɽ', + 11367 => 'ⱨ', + 11369 => 'ⱪ', + 11371 => 'ⱬ', + 11373 => 'ɑ', + 11374 => 'ɱ', + 11375 => 'ɐ', + 11376 => 'ɒ', + 11378 => 'ⱳ', + 11381 => 'ⱶ', + 11388 => 'j', + 11389 => 'v', + 11390 => 'ȿ', + 11391 => 'ɀ', + 11392 => 'ⲁ', + 11394 => 'ⲃ', + 11396 => 'ⲅ', + 11398 => 'ⲇ', + 11400 => 'ⲉ', + 11402 => 'ⲋ', + 11404 => 'ⲍ', + 11406 => 'ⲏ', + 11408 => 'ⲑ', + 11410 => 'ⲓ', + 11412 => 'ⲕ', + 11414 => 'ⲗ', + 11416 => 'ⲙ', + 11418 => 'ⲛ', + 11420 => 'ⲝ', + 11422 => 'ⲟ', + 11424 => 'ⲡ', + 11426 => 'ⲣ', + 11428 => 'ⲥ', + 11430 => 'ⲧ', + 11432 => 'ⲩ', + 11434 => 'ⲫ', + 11436 => 'ⲭ', + 11438 => 'ⲯ', + 11440 => 'ⲱ', + 11442 => 'ⲳ', + 11444 => 'ⲵ', + 11446 => 'ⲷ', + 11448 => 'ⲹ', + 11450 => 'ⲻ', + 11452 => 'ⲽ', + 11454 => 'ⲿ', + 11456 => 'ⳁ', + 11458 => 'ⳃ', + 11460 => 'ⳅ', + 11462 => 'ⳇ', + 11464 => 'ⳉ', + 11466 => 'ⳋ', + 11468 => 'ⳍ', + 11470 => 'ⳏ', + 11472 => 'ⳑ', + 11474 => 'ⳓ', + 11476 => 'ⳕ', + 11478 => 'ⳗ', + 11480 => 'ⳙ', + 11482 => 'ⳛ', + 11484 => 'ⳝ', + 11486 => 'ⳟ', + 11488 => 'ⳡ', + 11490 => 'ⳣ', + 11499 => 'ⳬ', + 11501 => 'ⳮ', + 11506 => 'ⳳ', + 11631 => 'ⵡ', + 11935 => '母', + 12019 => '龟', + 12032 => '一', + 12033 => '丨', + 12034 => '丶', + 12035 => '丿', + 12036 => '乙', + 12037 => '亅', + 12038 => '二', + 12039 => '亠', + 12040 => '人', + 12041 => '儿', + 12042 => '入', + 12043 => '八', + 12044 => '冂', + 12045 => '冖', + 12046 => '冫', + 12047 => '几', + 12048 => '凵', + 12049 => '刀', + 12050 => '力', + 12051 => '勹', + 12052 => '匕', + 12053 => '匚', + 12054 => '匸', + 12055 => '十', + 12056 => '卜', + 12057 => '卩', + 12058 => '厂', + 12059 => '厶', + 12060 => '又', + 12061 => '口', + 12062 => '囗', + 12063 => '土', + 12064 => '士', + 12065 => '夂', + 12066 => '夊', + 12067 => '夕', + 12068 => '大', + 12069 => '女', + 12070 => '子', + 12071 => '宀', + 12072 => '寸', + 12073 => '小', + 12074 => '尢', + 12075 => '尸', + 12076 => '屮', + 12077 => '山', + 12078 => '巛', + 12079 => '工', + 12080 => '己', + 12081 => '巾', + 12082 => '干', + 12083 => '幺', + 12084 => '广', + 12085 => '廴', + 12086 => '廾', + 12087 => '弋', + 12088 => '弓', + 12089 => '彐', + 12090 => '彡', + 12091 => '彳', + 12092 => '心', + 12093 => '戈', + 12094 => '戶', + 12095 => '手', + 12096 => '支', + 12097 => '攴', + 12098 => '文', + 12099 => '斗', + 12100 => '斤', + 12101 => '方', + 12102 => '无', + 12103 => '日', + 12104 => '曰', + 12105 => '月', + 12106 => '木', + 12107 => '欠', + 12108 => '止', + 12109 => '歹', + 12110 => '殳', + 12111 => '毋', + 12112 => '比', + 12113 => '毛', + 12114 => '氏', + 12115 => '气', + 12116 => '水', + 12117 => '火', + 12118 => '爪', + 12119 => '父', + 12120 => '爻', + 12121 => '爿', + 12122 => '片', + 12123 => '牙', + 12124 => '牛', + 12125 => '犬', + 12126 => '玄', + 12127 => '玉', + 12128 => '瓜', + 12129 => '瓦', + 12130 => '甘', + 12131 => '生', + 12132 => '用', + 12133 => '田', + 12134 => '疋', + 12135 => '疒', + 12136 => '癶', + 12137 => '白', + 12138 => '皮', + 12139 => '皿', + 12140 => '目', + 12141 => '矛', + 12142 => '矢', + 12143 => '石', + 12144 => '示', + 12145 => '禸', + 12146 => '禾', + 12147 => '穴', + 12148 => '立', + 12149 => '竹', + 12150 => '米', + 12151 => '糸', + 12152 => '缶', + 12153 => '网', + 12154 => '羊', + 12155 => '羽', + 12156 => '老', + 12157 => '而', + 12158 => '耒', + 12159 => '耳', + 12160 => '聿', + 12161 => '肉', + 12162 => '臣', + 12163 => '自', + 12164 => '至', + 12165 => '臼', + 12166 => '舌', + 12167 => '舛', + 12168 => '舟', + 12169 => '艮', + 12170 => '色', + 12171 => '艸', + 12172 => '虍', + 12173 => '虫', + 12174 => '血', + 12175 => '行', + 12176 => '衣', + 12177 => '襾', + 12178 => '見', + 12179 => '角', + 12180 => '言', + 12181 => '谷', + 12182 => '豆', + 12183 => '豕', + 12184 => '豸', + 12185 => '貝', + 12186 => '赤', + 12187 => '走', + 12188 => '足', + 12189 => '身', + 12190 => '車', + 12191 => '辛', + 12192 => '辰', + 12193 => '辵', + 12194 => '邑', + 12195 => '酉', + 12196 => '釆', + 12197 => '里', + 12198 => '金', + 12199 => '長', + 12200 => '門', + 12201 => '阜', + 12202 => '隶', + 12203 => '隹', + 12204 => '雨', + 12205 => '靑', + 12206 => '非', + 12207 => '面', + 12208 => '革', + 12209 => '韋', + 12210 => '韭', + 12211 => '音', + 12212 => '頁', + 12213 => '風', + 12214 => '飛', + 12215 => '食', + 12216 => '首', + 12217 => '香', + 12218 => '馬', + 12219 => '骨', + 12220 => '高', + 12221 => '髟', + 12222 => '鬥', + 12223 => '鬯', + 12224 => '鬲', + 12225 => '鬼', + 12226 => '魚', + 12227 => '鳥', + 12228 => '鹵', + 12229 => '鹿', + 12230 => '麥', + 12231 => '麻', + 12232 => '黃', + 12233 => '黍', + 12234 => '黑', + 12235 => '黹', + 12236 => '黽', + 12237 => '鼎', + 12238 => '鼓', + 12239 => '鼠', + 12240 => '鼻', + 12241 => '齊', + 12242 => '齒', + 12243 => '龍', + 12244 => '龜', + 12245 => '龠', + 12290 => '.', + 12342 => '〒', + 12344 => '十', + 12345 => '卄', + 12346 => '卅', + 12447 => 'より', + 12543 => 'コト', + 12593 => 'ᄀ', + 12594 => 'ᄁ', + 12595 => 'ᆪ', + 12596 => 'ᄂ', + 12597 => 'ᆬ', + 12598 => 'ᆭ', + 12599 => 'ᄃ', + 12600 => 'ᄄ', + 12601 => 'ᄅ', + 12602 => 'ᆰ', + 12603 => 'ᆱ', + 12604 => 'ᆲ', + 12605 => 'ᆳ', + 12606 => 'ᆴ', + 12607 => 'ᆵ', + 12608 => 'ᄚ', + 12609 => 'ᄆ', + 12610 => 'ᄇ', + 12611 => 'ᄈ', + 12612 => 'ᄡ', + 12613 => 'ᄉ', + 12614 => 'ᄊ', + 12615 => 'ᄋ', + 12616 => 'ᄌ', + 12617 => 'ᄍ', + 12618 => 'ᄎ', + 12619 => 'ᄏ', + 12620 => 'ᄐ', + 12621 => 'ᄑ', + 12622 => 'ᄒ', + 12623 => 'ᅡ', + 12624 => 'ᅢ', + 12625 => 'ᅣ', + 12626 => 'ᅤ', + 12627 => 'ᅥ', + 12628 => 'ᅦ', + 12629 => 'ᅧ', + 12630 => 'ᅨ', + 12631 => 'ᅩ', + 12632 => 'ᅪ', + 12633 => 'ᅫ', + 12634 => 'ᅬ', + 12635 => 'ᅭ', + 12636 => 'ᅮ', + 12637 => 'ᅯ', + 12638 => 'ᅰ', + 12639 => 'ᅱ', + 12640 => 'ᅲ', + 12641 => 'ᅳ', + 12642 => 'ᅴ', + 12643 => 'ᅵ', + 12645 => 'ᄔ', + 12646 => 'ᄕ', + 12647 => 'ᇇ', + 12648 => 'ᇈ', + 12649 => 'ᇌ', + 12650 => 'ᇎ', + 12651 => 'ᇓ', + 12652 => 'ᇗ', + 12653 => 'ᇙ', + 12654 => 'ᄜ', + 12655 => 'ᇝ', + 12656 => 'ᇟ', + 12657 => 'ᄝ', + 12658 => 'ᄞ', + 12659 => 'ᄠ', + 12660 => 'ᄢ', + 12661 => 'ᄣ', + 12662 => 'ᄧ', + 12663 => 'ᄩ', + 12664 => 'ᄫ', + 12665 => 'ᄬ', + 12666 => 'ᄭ', + 12667 => 'ᄮ', + 12668 => 'ᄯ', + 12669 => 'ᄲ', + 12670 => 'ᄶ', + 12671 => 'ᅀ', + 12672 => 'ᅇ', + 12673 => 'ᅌ', + 12674 => 'ᇱ', + 12675 => 'ᇲ', + 12676 => 'ᅗ', + 12677 => 'ᅘ', + 12678 => 'ᅙ', + 12679 => 'ᆄ', + 12680 => 'ᆅ', + 12681 => 'ᆈ', + 12682 => 'ᆑ', + 12683 => 'ᆒ', + 12684 => 'ᆔ', + 12685 => 'ᆞ', + 12686 => 'ᆡ', + 12690 => '一', + 12691 => '二', + 12692 => '三', + 12693 => '四', + 12694 => '上', + 12695 => '中', + 12696 => '下', + 12697 => '甲', + 12698 => '乙', + 12699 => '丙', + 12700 => '丁', + 12701 => '天', + 12702 => '地', + 12703 => '人', + 12868 => '問', + 12869 => '幼', + 12870 => '文', + 12871 => '箏', + 12880 => 'pte', + 12881 => '21', + 12882 => '22', + 12883 => '23', + 12884 => '24', + 12885 => '25', + 12886 => '26', + 12887 => '27', + 12888 => '28', + 12889 => '29', + 12890 => '30', + 12891 => '31', + 12892 => '32', + 12893 => '33', + 12894 => '34', + 12895 => '35', + 12896 => 'ᄀ', + 12897 => 'ᄂ', + 12898 => 'ᄃ', + 12899 => 'ᄅ', + 12900 => 'ᄆ', + 12901 => 'ᄇ', + 12902 => 'ᄉ', + 12903 => 'ᄋ', + 12904 => 'ᄌ', + 12905 => 'ᄎ', + 12906 => 'ᄏ', + 12907 => 'ᄐ', + 12908 => 'ᄑ', + 12909 => 'ᄒ', + 12910 => '가', + 12911 => '나', + 12912 => '다', + 12913 => '라', + 12914 => '마', + 12915 => '바', + 12916 => '사', + 12917 => '아', + 12918 => '자', + 12919 => '차', + 12920 => '카', + 12921 => '타', + 12922 => '파', + 12923 => '하', + 12924 => '참고', + 12925 => '주의', + 12926 => '우', + 12928 => '一', + 12929 => '二', + 12930 => '三', + 12931 => '四', + 12932 => '五', + 12933 => '六', + 12934 => '七', + 12935 => '八', + 12936 => '九', + 12937 => '十', + 12938 => '月', + 12939 => '火', + 12940 => '水', + 12941 => '木', + 12942 => '金', + 12943 => '土', + 12944 => '日', + 12945 => '株', + 12946 => '有', + 12947 => '社', + 12948 => '名', + 12949 => '特', + 12950 => '財', + 12951 => '祝', + 12952 => '労', + 12953 => '秘', + 12954 => '男', + 12955 => '女', + 12956 => '適', + 12957 => '優', + 12958 => '印', + 12959 => '注', + 12960 => '項', + 12961 => '休', + 12962 => '写', + 12963 => '正', + 12964 => '上', + 12965 => '中', + 12966 => '下', + 12967 => '左', + 12968 => '右', + 12969 => '医', + 12970 => '宗', + 12971 => '学', + 12972 => '監', + 12973 => '企', + 12974 => '資', + 12975 => '協', + 12976 => '夜', + 12977 => '36', + 12978 => '37', + 12979 => '38', + 12980 => '39', + 12981 => '40', + 12982 => '41', + 12983 => '42', + 12984 => '43', + 12985 => '44', + 12986 => '45', + 12987 => '46', + 12988 => '47', + 12989 => '48', + 12990 => '49', + 12991 => '50', + 12992 => '1月', + 12993 => '2月', + 12994 => '3月', + 12995 => '4月', + 12996 => '5月', + 12997 => '6月', + 12998 => '7月', + 12999 => '8月', + 13000 => '9月', + 13001 => '10月', + 13002 => '11月', + 13003 => '12月', + 13004 => 'hg', + 13005 => 'erg', + 13006 => 'ev', + 13007 => 'ltd', + 13008 => 'ア', + 13009 => 'イ', + 13010 => 'ウ', + 13011 => 'エ', + 13012 => 'オ', + 13013 => 'カ', + 13014 => 'キ', + 13015 => 'ク', + 13016 => 'ケ', + 13017 => 'コ', + 13018 => 'サ', + 13019 => 'シ', + 13020 => 'ス', + 13021 => 'セ', + 13022 => 'ソ', + 13023 => 'タ', + 13024 => 'チ', + 13025 => 'ツ', + 13026 => 'テ', + 13027 => 'ト', + 13028 => 'ナ', + 13029 => 'ニ', + 13030 => 'ヌ', + 13031 => 'ネ', + 13032 => 'ノ', + 13033 => 'ハ', + 13034 => 'ヒ', + 13035 => 'フ', + 13036 => 'ヘ', + 13037 => 'ホ', + 13038 => 'マ', + 13039 => 'ミ', + 13040 => 'ム', + 13041 => 'メ', + 13042 => 'モ', + 13043 => 'ヤ', + 13044 => 'ユ', + 13045 => 'ヨ', + 13046 => 'ラ', + 13047 => 'リ', + 13048 => 'ル', + 13049 => 'レ', + 13050 => 'ロ', + 13051 => 'ワ', + 13052 => 'ヰ', + 13053 => 'ヱ', + 13054 => 'ヲ', + 13055 => '令和', + 13056 => 'アパート', + 13057 => 'アルファ', + 13058 => 'アンペア', + 13059 => 'アール', + 13060 => 'イニング', + 13061 => 'インチ', + 13062 => 'ウォン', + 13063 => 'エスクード', + 13064 => 'エーカー', + 13065 => 'オンス', + 13066 => 'オーム', + 13067 => 'カイリ', + 13068 => 'カラット', + 13069 => 'カロリー', + 13070 => 'ガロン', + 13071 => 'ガンマ', + 13072 => 'ギガ', + 13073 => 'ギニー', + 13074 => 'キュリー', + 13075 => 'ギルダー', + 13076 => 'キロ', + 13077 => 'キログラム', + 13078 => 'キロメートル', + 13079 => 'キロワット', + 13080 => 'グラム', + 13081 => 'グラムトン', + 13082 => 'クルゼイロ', + 13083 => 'クローネ', + 13084 => 'ケース', + 13085 => 'コルナ', + 13086 => 'コーポ', + 13087 => 'サイクル', + 13088 => 'サンチーム', + 13089 => 'シリング', + 13090 => 'センチ', + 13091 => 'セント', + 13092 => 'ダース', + 13093 => 'デシ', + 13094 => 'ドル', + 13095 => 'トン', + 13096 => 'ナノ', + 13097 => 'ノット', + 13098 => 'ハイツ', + 13099 => 'パーセント', + 13100 => 'パーツ', + 13101 => 'バーレル', + 13102 => 'ピアストル', + 13103 => 'ピクル', + 13104 => 'ピコ', + 13105 => 'ビル', + 13106 => 'ファラッド', + 13107 => 'フィート', + 13108 => 'ブッシェル', + 13109 => 'フラン', + 13110 => 'ヘクタール', + 13111 => 'ペソ', + 13112 => 'ペニヒ', + 13113 => 'ヘルツ', + 13114 => 'ペンス', + 13115 => 'ページ', + 13116 => 'ベータ', + 13117 => 'ポイント', + 13118 => 'ボルト', + 13119 => 'ホン', + 13120 => 'ポンド', + 13121 => 'ホール', + 13122 => 'ホーン', + 13123 => 'マイクロ', + 13124 => 'マイル', + 13125 => 'マッハ', + 13126 => 'マルク', + 13127 => 'マンション', + 13128 => 'ミクロン', + 13129 => 'ミリ', + 13130 => 'ミリバール', + 13131 => 'メガ', + 13132 => 'メガトン', + 13133 => 'メートル', + 13134 => 'ヤード', + 13135 => 'ヤール', + 13136 => 'ユアン', + 13137 => 'リットル', + 13138 => 'リラ', + 13139 => 'ルピー', + 13140 => 'ルーブル', + 13141 => 'レム', + 13142 => 'レントゲン', + 13143 => 'ワット', + 13144 => '0点', + 13145 => '1点', + 13146 => '2点', + 13147 => '3点', + 13148 => '4点', + 13149 => '5点', + 13150 => '6点', + 13151 => '7点', + 13152 => '8点', + 13153 => '9点', + 13154 => '10点', + 13155 => '11点', + 13156 => '12点', + 13157 => '13点', + 13158 => '14点', + 13159 => '15点', + 13160 => '16点', + 13161 => '17点', + 13162 => '18点', + 13163 => '19点', + 13164 => '20点', + 13165 => '21点', + 13166 => '22点', + 13167 => '23点', + 13168 => '24点', + 13169 => 'hpa', + 13170 => 'da', + 13171 => 'au', + 13172 => 'bar', + 13173 => 'ov', + 13174 => 'pc', + 13175 => 'dm', + 13176 => 'dm2', + 13177 => 'dm3', + 13178 => 'iu', + 13179 => '平成', + 13180 => '昭和', + 13181 => '大正', + 13182 => '明治', + 13183 => '株式会社', + 13184 => 'pa', + 13185 => 'na', + 13186 => 'μa', + 13187 => 'ma', + 13188 => 'ka', + 13189 => 'kb', + 13190 => 'mb', + 13191 => 'gb', + 13192 => 'cal', + 13193 => 'kcal', + 13194 => 'pf', + 13195 => 'nf', + 13196 => 'μf', + 13197 => 'μg', + 13198 => 'mg', + 13199 => 'kg', + 13200 => 'hz', + 13201 => 'khz', + 13202 => 'mhz', + 13203 => 'ghz', + 13204 => 'thz', + 13205 => 'μl', + 13206 => 'ml', + 13207 => 'dl', + 13208 => 'kl', + 13209 => 'fm', + 13210 => 'nm', + 13211 => 'μm', + 13212 => 'mm', + 13213 => 'cm', + 13214 => 'km', + 13215 => 'mm2', + 13216 => 'cm2', + 13217 => 'm2', + 13218 => 'km2', + 13219 => 'mm3', + 13220 => 'cm3', + 13221 => 'm3', + 13222 => 'km3', + 13223 => 'm∕s', + 13224 => 'm∕s2', + 13225 => 'pa', + 13226 => 'kpa', + 13227 => 'mpa', + 13228 => 'gpa', + 13229 => 'rad', + 13230 => 'rad∕s', + 13231 => 'rad∕s2', + 13232 => 'ps', + 13233 => 'ns', + 13234 => 'μs', + 13235 => 'ms', + 13236 => 'pv', + 13237 => 'nv', + 13238 => 'μv', + 13239 => 'mv', + 13240 => 'kv', + 13241 => 'mv', + 13242 => 'pw', + 13243 => 'nw', + 13244 => 'μw', + 13245 => 'mw', + 13246 => 'kw', + 13247 => 'mw', + 13248 => 'kω', + 13249 => 'mω', + 13251 => 'bq', + 13252 => 'cc', + 13253 => 'cd', + 13254 => 'c∕kg', + 13256 => 'db', + 13257 => 'gy', + 13258 => 'ha', + 13259 => 'hp', + 13260 => 'in', + 13261 => 'kk', + 13262 => 'km', + 13263 => 'kt', + 13264 => 'lm', + 13265 => 'ln', + 13266 => 'log', + 13267 => 'lx', + 13268 => 'mb', + 13269 => 'mil', + 13270 => 'mol', + 13271 => 'ph', + 13273 => 'ppm', + 13274 => 'pr', + 13275 => 'sr', + 13276 => 'sv', + 13277 => 'wb', + 13278 => 'v∕m', + 13279 => 'a∕m', + 13280 => '1日', + 13281 => '2日', + 13282 => '3日', + 13283 => '4日', + 13284 => '5日', + 13285 => '6日', + 13286 => '7日', + 13287 => '8日', + 13288 => '9日', + 13289 => '10日', + 13290 => '11日', + 13291 => '12日', + 13292 => '13日', + 13293 => '14日', + 13294 => '15日', + 13295 => '16日', + 13296 => '17日', + 13297 => '18日', + 13298 => '19日', + 13299 => '20日', + 13300 => '21日', + 13301 => '22日', + 13302 => '23日', + 13303 => '24日', + 13304 => '25日', + 13305 => '26日', + 13306 => '27日', + 13307 => '28日', + 13308 => '29日', + 13309 => '30日', + 13310 => '31日', + 13311 => 'gal', + 42560 => 'ꙁ', + 42562 => 'ꙃ', + 42564 => 'ꙅ', + 42566 => 'ꙇ', + 42568 => 'ꙉ', + 42570 => 'ꙋ', + 42572 => 'ꙍ', + 42574 => 'ꙏ', + 42576 => 'ꙑ', + 42578 => 'ꙓ', + 42580 => 'ꙕ', + 42582 => 'ꙗ', + 42584 => 'ꙙ', + 42586 => 'ꙛ', + 42588 => 'ꙝ', + 42590 => 'ꙟ', + 42592 => 'ꙡ', + 42594 => 'ꙣ', + 42596 => 'ꙥ', + 42598 => 'ꙧ', + 42600 => 'ꙩ', + 42602 => 'ꙫ', + 42604 => 'ꙭ', + 42624 => 'ꚁ', + 42626 => 'ꚃ', + 42628 => 'ꚅ', + 42630 => 'ꚇ', + 42632 => 'ꚉ', + 42634 => 'ꚋ', + 42636 => 'ꚍ', + 42638 => 'ꚏ', + 42640 => 'ꚑ', + 42642 => 'ꚓ', + 42644 => 'ꚕ', + 42646 => 'ꚗ', + 42648 => 'ꚙ', + 42650 => 'ꚛ', + 42652 => 'ъ', + 42653 => 'ь', + 42786 => 'ꜣ', + 42788 => 'ꜥ', + 42790 => 'ꜧ', + 42792 => 'ꜩ', + 42794 => 'ꜫ', + 42796 => 'ꜭ', + 42798 => 'ꜯ', + 42802 => 'ꜳ', + 42804 => 'ꜵ', + 42806 => 'ꜷ', + 42808 => 'ꜹ', + 42810 => 'ꜻ', + 42812 => 'ꜽ', + 42814 => 'ꜿ', + 42816 => 'ꝁ', + 42818 => 'ꝃ', + 42820 => 'ꝅ', + 42822 => 'ꝇ', + 42824 => 'ꝉ', + 42826 => 'ꝋ', + 42828 => 'ꝍ', + 42830 => 'ꝏ', + 42832 => 'ꝑ', + 42834 => 'ꝓ', + 42836 => 'ꝕ', + 42838 => 'ꝗ', + 42840 => 'ꝙ', + 42842 => 'ꝛ', + 42844 => 'ꝝ', + 42846 => 'ꝟ', + 42848 => 'ꝡ', + 42850 => 'ꝣ', + 42852 => 'ꝥ', + 42854 => 'ꝧ', + 42856 => 'ꝩ', + 42858 => 'ꝫ', + 42860 => 'ꝭ', + 42862 => 'ꝯ', + 42864 => 'ꝯ', + 42873 => 'ꝺ', + 42875 => 'ꝼ', + 42877 => 'ᵹ', + 42878 => 'ꝿ', + 42880 => 'ꞁ', + 42882 => 'ꞃ', + 42884 => 'ꞅ', + 42886 => 'ꞇ', + 42891 => 'ꞌ', + 42893 => 'ɥ', + 42896 => 'ꞑ', + 42898 => 'ꞓ', + 42902 => 'ꞗ', + 42904 => 'ꞙ', + 42906 => 'ꞛ', + 42908 => 'ꞝ', + 42910 => 'ꞟ', + 42912 => 'ꞡ', + 42914 => 'ꞣ', + 42916 => 'ꞥ', + 42918 => 'ꞧ', + 42920 => 'ꞩ', + 42922 => 'ɦ', + 42923 => 'ɜ', + 42924 => 'ɡ', + 42925 => 'ɬ', + 42926 => 'ɪ', + 42928 => 'ʞ', + 42929 => 'ʇ', + 42930 => 'ʝ', + 42931 => 'ꭓ', + 42932 => 'ꞵ', + 42934 => 'ꞷ', + 42936 => 'ꞹ', + 42938 => 'ꞻ', + 42940 => 'ꞽ', + 42942 => 'ꞿ', + 42946 => 'ꟃ', + 42948 => 'ꞔ', + 42949 => 'ʂ', + 42950 => 'ᶎ', + 42951 => 'ꟈ', + 42953 => 'ꟊ', + 42997 => 'ꟶ', + 43000 => 'ħ', + 43001 => 'œ', + 43868 => 'ꜧ', + 43869 => 'ꬷ', + 43870 => 'ɫ', + 43871 => 'ꭒ', + 43881 => 'ʍ', + 43888 => 'Ꭰ', + 43889 => 'Ꭱ', + 43890 => 'Ꭲ', + 43891 => 'Ꭳ', + 43892 => 'Ꭴ', + 43893 => 'Ꭵ', + 43894 => 'Ꭶ', + 43895 => 'Ꭷ', + 43896 => 'Ꭸ', + 43897 => 'Ꭹ', + 43898 => 'Ꭺ', + 43899 => 'Ꭻ', + 43900 => 'Ꭼ', + 43901 => 'Ꭽ', + 43902 => 'Ꭾ', + 43903 => 'Ꭿ', + 43904 => 'Ꮀ', + 43905 => 'Ꮁ', + 43906 => 'Ꮂ', + 43907 => 'Ꮃ', + 43908 => 'Ꮄ', + 43909 => 'Ꮅ', + 43910 => 'Ꮆ', + 43911 => 'Ꮇ', + 43912 => 'Ꮈ', + 43913 => 'Ꮉ', + 43914 => 'Ꮊ', + 43915 => 'Ꮋ', + 43916 => 'Ꮌ', + 43917 => 'Ꮍ', + 43918 => 'Ꮎ', + 43919 => 'Ꮏ', + 43920 => 'Ꮐ', + 43921 => 'Ꮑ', + 43922 => 'Ꮒ', + 43923 => 'Ꮓ', + 43924 => 'Ꮔ', + 43925 => 'Ꮕ', + 43926 => 'Ꮖ', + 43927 => 'Ꮗ', + 43928 => 'Ꮘ', + 43929 => 'Ꮙ', + 43930 => 'Ꮚ', + 43931 => 'Ꮛ', + 43932 => 'Ꮜ', + 43933 => 'Ꮝ', + 43934 => 'Ꮞ', + 43935 => 'Ꮟ', + 43936 => 'Ꮠ', + 43937 => 'Ꮡ', + 43938 => 'Ꮢ', + 43939 => 'Ꮣ', + 43940 => 'Ꮤ', + 43941 => 'Ꮥ', + 43942 => 'Ꮦ', + 43943 => 'Ꮧ', + 43944 => 'Ꮨ', + 43945 => 'Ꮩ', + 43946 => 'Ꮪ', + 43947 => 'Ꮫ', + 43948 => 'Ꮬ', + 43949 => 'Ꮭ', + 43950 => 'Ꮮ', + 43951 => 'Ꮯ', + 43952 => 'Ꮰ', + 43953 => 'Ꮱ', + 43954 => 'Ꮲ', + 43955 => 'Ꮳ', + 43956 => 'Ꮴ', + 43957 => 'Ꮵ', + 43958 => 'Ꮶ', + 43959 => 'Ꮷ', + 43960 => 'Ꮸ', + 43961 => 'Ꮹ', + 43962 => 'Ꮺ', + 43963 => 'Ꮻ', + 43964 => 'Ꮼ', + 43965 => 'Ꮽ', + 43966 => 'Ꮾ', + 43967 => 'Ꮿ', + 63744 => '豈', + 63745 => '更', + 63746 => '車', + 63747 => '賈', + 63748 => '滑', + 63749 => '串', + 63750 => '句', + 63751 => '龜', + 63752 => '龜', + 63753 => '契', + 63754 => '金', + 63755 => '喇', + 63756 => '奈', + 63757 => '懶', + 63758 => '癩', + 63759 => '羅', + 63760 => '蘿', + 63761 => '螺', + 63762 => '裸', + 63763 => '邏', + 63764 => '樂', + 63765 => '洛', + 63766 => '烙', + 63767 => '珞', + 63768 => '落', + 63769 => '酪', + 63770 => '駱', + 63771 => '亂', + 63772 => '卵', + 63773 => '欄', + 63774 => '爛', + 63775 => '蘭', + 63776 => '鸞', + 63777 => '嵐', + 63778 => '濫', + 63779 => '藍', + 63780 => '襤', + 63781 => '拉', + 63782 => '臘', + 63783 => '蠟', + 63784 => '廊', + 63785 => '朗', + 63786 => '浪', + 63787 => '狼', + 63788 => '郎', + 63789 => '來', + 63790 => '冷', + 63791 => '勞', + 63792 => '擄', + 63793 => '櫓', + 63794 => '爐', + 63795 => '盧', + 63796 => '老', + 63797 => '蘆', + 63798 => '虜', + 63799 => '路', + 63800 => '露', + 63801 => '魯', + 63802 => '鷺', + 63803 => '碌', + 63804 => '祿', + 63805 => '綠', + 63806 => '菉', + 63807 => '錄', + 63808 => '鹿', + 63809 => '論', + 63810 => '壟', + 63811 => '弄', + 63812 => '籠', + 63813 => '聾', + 63814 => '牢', + 63815 => '磊', + 63816 => '賂', + 63817 => '雷', + 63818 => '壘', + 63819 => '屢', + 63820 => '樓', + 63821 => '淚', + 63822 => '漏', + 63823 => '累', + 63824 => '縷', + 63825 => '陋', + 63826 => '勒', + 63827 => '肋', + 63828 => '凜', + 63829 => '凌', + 63830 => '稜', + 63831 => '綾', + 63832 => '菱', + 63833 => '陵', + 63834 => '讀', + 63835 => '拏', + 63836 => '樂', + 63837 => '諾', + 63838 => '丹', + 63839 => '寧', + 63840 => '怒', + 63841 => '率', + 63842 => '異', + 63843 => '北', + 63844 => '磻', + 63845 => '便', + 63846 => '復', + 63847 => '不', + 63848 => '泌', + 63849 => '數', + 63850 => '索', + 63851 => '參', + 63852 => '塞', + 63853 => '省', + 63854 => '葉', + 63855 => '說', + 63856 => '殺', + 63857 => '辰', + 63858 => '沈', + 63859 => '拾', + 63860 => '若', + 63861 => '掠', + 63862 => '略', + 63863 => '亮', + 63864 => '兩', + 63865 => '凉', + 63866 => '梁', + 63867 => '糧', + 63868 => '良', + 63869 => '諒', + 63870 => '量', + 63871 => '勵', + 63872 => '呂', + 63873 => '女', + 63874 => '廬', + 63875 => '旅', + 63876 => '濾', + 63877 => '礪', + 63878 => '閭', + 63879 => '驪', + 63880 => '麗', + 63881 => '黎', + 63882 => '力', + 63883 => '曆', + 63884 => '歷', + 63885 => '轢', + 63886 => '年', + 63887 => '憐', + 63888 => '戀', + 63889 => '撚', + 63890 => '漣', + 63891 => '煉', + 63892 => '璉', + 63893 => '秊', + 63894 => '練', + 63895 => '聯', + 63896 => '輦', + 63897 => '蓮', + 63898 => '連', + 63899 => '鍊', + 63900 => '列', + 63901 => '劣', + 63902 => '咽', + 63903 => '烈', + 63904 => '裂', + 63905 => '說', + 63906 => '廉', + 63907 => '念', + 63908 => '捻', + 63909 => '殮', + 63910 => '簾', + 63911 => '獵', + 63912 => '令', + 63913 => '囹', + 63914 => '寧', + 63915 => '嶺', + 63916 => '怜', + 63917 => '玲', + 63918 => '瑩', + 63919 => '羚', + 63920 => '聆', + 63921 => '鈴', + 63922 => '零', + 63923 => '靈', + 63924 => '領', + 63925 => '例', + 63926 => '禮', + 63927 => '醴', + 63928 => '隸', + 63929 => '惡', + 63930 => '了', + 63931 => '僚', + 63932 => '寮', + 63933 => '尿', + 63934 => '料', + 63935 => '樂', + 63936 => '燎', + 63937 => '療', + 63938 => '蓼', + 63939 => '遼', + 63940 => '龍', + 63941 => '暈', + 63942 => '阮', + 63943 => '劉', + 63944 => '杻', + 63945 => '柳', + 63946 => '流', + 63947 => '溜', + 63948 => '琉', + 63949 => '留', + 63950 => '硫', + 63951 => '紐', + 63952 => '類', + 63953 => '六', + 63954 => '戮', + 63955 => '陸', + 63956 => '倫', + 63957 => '崙', + 63958 => '淪', + 63959 => '輪', + 63960 => '律', + 63961 => '慄', + 63962 => '栗', + 63963 => '率', + 63964 => '隆', + 63965 => '利', + 63966 => '吏', + 63967 => '履', + 63968 => '易', + 63969 => '李', + 63970 => '梨', + 63971 => '泥', + 63972 => '理', + 63973 => '痢', + 63974 => '罹', + 63975 => '裏', + 63976 => '裡', + 63977 => '里', + 63978 => '離', + 63979 => '匿', + 63980 => '溺', + 63981 => '吝', + 63982 => '燐', + 63983 => '璘', + 63984 => '藺', + 63985 => '隣', + 63986 => '鱗', + 63987 => '麟', + 63988 => '林', + 63989 => '淋', + 63990 => '臨', + 63991 => '立', + 63992 => '笠', + 63993 => '粒', + 63994 => '狀', + 63995 => '炙', + 63996 => '識', + 63997 => '什', + 63998 => '茶', + 63999 => '刺', + 64000 => '切', + 64001 => '度', + 64002 => '拓', + 64003 => '糖', + 64004 => '宅', + 64005 => '洞', + 64006 => '暴', + 64007 => '輻', + 64008 => '行', + 64009 => '降', + 64010 => '見', + 64011 => '廓', + 64012 => '兀', + 64013 => '嗀', + 64016 => '塚', + 64018 => '晴', + 64021 => '凞', + 64022 => '猪', + 64023 => '益', + 64024 => '礼', + 64025 => '神', + 64026 => '祥', + 64027 => '福', + 64028 => '靖', + 64029 => '精', + 64030 => '羽', + 64032 => '蘒', + 64034 => '諸', + 64037 => '逸', + 64038 => '都', + 64042 => '飯', + 64043 => '飼', + 64044 => '館', + 64045 => '鶴', + 64046 => '郞', + 64047 => '隷', + 64048 => '侮', + 64049 => '僧', + 64050 => '免', + 64051 => '勉', + 64052 => '勤', + 64053 => '卑', + 64054 => '喝', + 64055 => '嘆', + 64056 => '器', + 64057 => '塀', + 64058 => '墨', + 64059 => '層', + 64060 => '屮', + 64061 => '悔', + 64062 => '慨', + 64063 => '憎', + 64064 => '懲', + 64065 => '敏', + 64066 => '既', + 64067 => '暑', + 64068 => '梅', + 64069 => '海', + 64070 => '渚', + 64071 => '漢', + 64072 => '煮', + 64073 => '爫', + 64074 => '琢', + 64075 => '碑', + 64076 => '社', + 64077 => '祉', + 64078 => '祈', + 64079 => '祐', + 64080 => '祖', + 64081 => '祝', + 64082 => '禍', + 64083 => '禎', + 64084 => '穀', + 64085 => '突', + 64086 => '節', + 64087 => '練', + 64088 => '縉', + 64089 => '繁', + 64090 => '署', + 64091 => '者', + 64092 => '臭', + 64093 => '艹', + 64094 => '艹', + 64095 => '著', + 64096 => '褐', + 64097 => '視', + 64098 => '謁', + 64099 => '謹', + 64100 => '賓', + 64101 => '贈', + 64102 => '辶', + 64103 => '逸', + 64104 => '難', + 64105 => '響', + 64106 => '頻', + 64107 => '恵', + 64108 => '𤋮', + 64109 => '舘', + 64112 => '並', + 64113 => '况', + 64114 => '全', + 64115 => '侀', + 64116 => '充', + 64117 => '冀', + 64118 => '勇', + 64119 => '勺', + 64120 => '喝', + 64121 => '啕', + 64122 => '喙', + 64123 => '嗢', + 64124 => '塚', + 64125 => '墳', + 64126 => '奄', + 64127 => '奔', + 64128 => '婢', + 64129 => '嬨', + 64130 => '廒', + 64131 => '廙', + 64132 => '彩', + 64133 => '徭', + 64134 => '惘', + 64135 => '慎', + 64136 => '愈', + 64137 => '憎', + 64138 => '慠', + 64139 => '懲', + 64140 => '戴', + 64141 => '揄', + 64142 => '搜', + 64143 => '摒', + 64144 => '敖', + 64145 => '晴', + 64146 => '朗', + 64147 => '望', + 64148 => '杖', + 64149 => '歹', + 64150 => '殺', + 64151 => '流', + 64152 => '滛', + 64153 => '滋', + 64154 => '漢', + 64155 => '瀞', + 64156 => '煮', + 64157 => '瞧', + 64158 => '爵', + 64159 => '犯', + 64160 => '猪', + 64161 => '瑱', + 64162 => '甆', + 64163 => '画', + 64164 => '瘝', + 64165 => '瘟', + 64166 => '益', + 64167 => '盛', + 64168 => '直', + 64169 => '睊', + 64170 => '着', + 64171 => '磌', + 64172 => '窱', + 64173 => '節', + 64174 => '类', + 64175 => '絛', + 64176 => '練', + 64177 => '缾', + 64178 => '者', + 64179 => '荒', + 64180 => '華', + 64181 => '蝹', + 64182 => '襁', + 64183 => '覆', + 64184 => '視', + 64185 => '調', + 64186 => '諸', + 64187 => '請', + 64188 => '謁', + 64189 => '諾', + 64190 => '諭', + 64191 => '謹', + 64192 => '變', + 64193 => '贈', + 64194 => '輸', + 64195 => '遲', + 64196 => '醙', + 64197 => '鉶', + 64198 => '陼', + 64199 => '難', + 64200 => '靖', + 64201 => '韛', + 64202 => '響', + 64203 => '頋', + 64204 => '頻', + 64205 => '鬒', + 64206 => '龜', + 64207 => '𢡊', + 64208 => '𢡄', + 64209 => '𣏕', + 64210 => '㮝', + 64211 => '䀘', + 64212 => '䀹', + 64213 => '𥉉', + 64214 => '𥳐', + 64215 => '𧻓', + 64216 => '齃', + 64217 => '龎', + 64256 => 'ff', + 64257 => 'fi', + 64258 => 'fl', + 64259 => 'ffi', + 64260 => 'ffl', + 64261 => 'st', + 64262 => 'st', + 64275 => 'մն', + 64276 => 'մե', + 64277 => 'մի', + 64278 => 'վն', + 64279 => 'մխ', + 64285 => 'יִ', + 64287 => 'ײַ', + 64288 => 'ע', + 64289 => 'א', + 64290 => 'ד', + 64291 => 'ה', + 64292 => 'כ', + 64293 => 'ל', + 64294 => 'ם', + 64295 => 'ר', + 64296 => 'ת', + 64298 => 'שׁ', + 64299 => 'שׂ', + 64300 => 'שּׁ', + 64301 => 'שּׂ', + 64302 => 'אַ', + 64303 => 'אָ', + 64304 => 'אּ', + 64305 => 'בּ', + 64306 => 'גּ', + 64307 => 'דּ', + 64308 => 'הּ', + 64309 => 'וּ', + 64310 => 'זּ', + 64312 => 'טּ', + 64313 => 'יּ', + 64314 => 'ךּ', + 64315 => 'כּ', + 64316 => 'לּ', + 64318 => 'מּ', + 64320 => 'נּ', + 64321 => 'סּ', + 64323 => 'ףּ', + 64324 => 'פּ', + 64326 => 'צּ', + 64327 => 'קּ', + 64328 => 'רּ', + 64329 => 'שּ', + 64330 => 'תּ', + 64331 => 'וֹ', + 64332 => 'בֿ', + 64333 => 'כֿ', + 64334 => 'פֿ', + 64335 => 'אל', + 64336 => 'ٱ', + 64337 => 'ٱ', + 64338 => 'ٻ', + 64339 => 'ٻ', + 64340 => 'ٻ', + 64341 => 'ٻ', + 64342 => 'پ', + 64343 => 'پ', + 64344 => 'پ', + 64345 => 'پ', + 64346 => 'ڀ', + 64347 => 'ڀ', + 64348 => 'ڀ', + 64349 => 'ڀ', + 64350 => 'ٺ', + 64351 => 'ٺ', + 64352 => 'ٺ', + 64353 => 'ٺ', + 64354 => 'ٿ', + 64355 => 'ٿ', + 64356 => 'ٿ', + 64357 => 'ٿ', + 64358 => 'ٹ', + 64359 => 'ٹ', + 64360 => 'ٹ', + 64361 => 'ٹ', + 64362 => 'ڤ', + 64363 => 'ڤ', + 64364 => 'ڤ', + 64365 => 'ڤ', + 64366 => 'ڦ', + 64367 => 'ڦ', + 64368 => 'ڦ', + 64369 => 'ڦ', + 64370 => 'ڄ', + 64371 => 'ڄ', + 64372 => 'ڄ', + 64373 => 'ڄ', + 64374 => 'ڃ', + 64375 => 'ڃ', + 64376 => 'ڃ', + 64377 => 'ڃ', + 64378 => 'چ', + 64379 => 'چ', + 64380 => 'چ', + 64381 => 'چ', + 64382 => 'ڇ', + 64383 => 'ڇ', + 64384 => 'ڇ', + 64385 => 'ڇ', + 64386 => 'ڍ', + 64387 => 'ڍ', + 64388 => 'ڌ', + 64389 => 'ڌ', + 64390 => 'ڎ', + 64391 => 'ڎ', + 64392 => 'ڈ', + 64393 => 'ڈ', + 64394 => 'ژ', + 64395 => 'ژ', + 64396 => 'ڑ', + 64397 => 'ڑ', + 64398 => 'ک', + 64399 => 'ک', + 64400 => 'ک', + 64401 => 'ک', + 64402 => 'گ', + 64403 => 'گ', + 64404 => 'گ', + 64405 => 'گ', + 64406 => 'ڳ', + 64407 => 'ڳ', + 64408 => 'ڳ', + 64409 => 'ڳ', + 64410 => 'ڱ', + 64411 => 'ڱ', + 64412 => 'ڱ', + 64413 => 'ڱ', + 64414 => 'ں', + 64415 => 'ں', + 64416 => 'ڻ', + 64417 => 'ڻ', + 64418 => 'ڻ', + 64419 => 'ڻ', + 64420 => 'ۀ', + 64421 => 'ۀ', + 64422 => 'ہ', + 64423 => 'ہ', + 64424 => 'ہ', + 64425 => 'ہ', + 64426 => 'ھ', + 64427 => 'ھ', + 64428 => 'ھ', + 64429 => 'ھ', + 64430 => 'ے', + 64431 => 'ے', + 64432 => 'ۓ', + 64433 => 'ۓ', + 64467 => 'ڭ', + 64468 => 'ڭ', + 64469 => 'ڭ', + 64470 => 'ڭ', + 64471 => 'ۇ', + 64472 => 'ۇ', + 64473 => 'ۆ', + 64474 => 'ۆ', + 64475 => 'ۈ', + 64476 => 'ۈ', + 64477 => 'ۇٴ', + 64478 => 'ۋ', + 64479 => 'ۋ', + 64480 => 'ۅ', + 64481 => 'ۅ', + 64482 => 'ۉ', + 64483 => 'ۉ', + 64484 => 'ې', + 64485 => 'ې', + 64486 => 'ې', + 64487 => 'ې', + 64488 => 'ى', + 64489 => 'ى', + 64490 => 'ئا', + 64491 => 'ئا', + 64492 => 'ئە', + 64493 => 'ئە', + 64494 => 'ئو', + 64495 => 'ئو', + 64496 => 'ئۇ', + 64497 => 'ئۇ', + 64498 => 'ئۆ', + 64499 => 'ئۆ', + 64500 => 'ئۈ', + 64501 => 'ئۈ', + 64502 => 'ئې', + 64503 => 'ئې', + 64504 => 'ئې', + 64505 => 'ئى', + 64506 => 'ئى', + 64507 => 'ئى', + 64508 => 'ی', + 64509 => 'ی', + 64510 => 'ی', + 64511 => 'ی', + 64512 => 'ئج', + 64513 => 'ئح', + 64514 => 'ئم', + 64515 => 'ئى', + 64516 => 'ئي', + 64517 => 'بج', + 64518 => 'بح', + 64519 => 'بخ', + 64520 => 'بم', + 64521 => 'بى', + 64522 => 'بي', + 64523 => 'تج', + 64524 => 'تح', + 64525 => 'تخ', + 64526 => 'تم', + 64527 => 'تى', + 64528 => 'تي', + 64529 => 'ثج', + 64530 => 'ثم', + 64531 => 'ثى', + 64532 => 'ثي', + 64533 => 'جح', + 64534 => 'جم', + 64535 => 'حج', + 64536 => 'حم', + 64537 => 'خج', + 64538 => 'خح', + 64539 => 'خم', + 64540 => 'سج', + 64541 => 'سح', + 64542 => 'سخ', + 64543 => 'سم', + 64544 => 'صح', + 64545 => 'صم', + 64546 => 'ضج', + 64547 => 'ضح', + 64548 => 'ضخ', + 64549 => 'ضم', + 64550 => 'طح', + 64551 => 'طم', + 64552 => 'ظم', + 64553 => 'عج', + 64554 => 'عم', + 64555 => 'غج', + 64556 => 'غم', + 64557 => 'فج', + 64558 => 'فح', + 64559 => 'فخ', + 64560 => 'فم', + 64561 => 'فى', + 64562 => 'في', + 64563 => 'قح', + 64564 => 'قم', + 64565 => 'قى', + 64566 => 'قي', + 64567 => 'كا', + 64568 => 'كج', + 64569 => 'كح', + 64570 => 'كخ', + 64571 => 'كل', + 64572 => 'كم', + 64573 => 'كى', + 64574 => 'كي', + 64575 => 'لج', + 64576 => 'لح', + 64577 => 'لخ', + 64578 => 'لم', + 64579 => 'لى', + 64580 => 'لي', + 64581 => 'مج', + 64582 => 'مح', + 64583 => 'مخ', + 64584 => 'مم', + 64585 => 'مى', + 64586 => 'مي', + 64587 => 'نج', + 64588 => 'نح', + 64589 => 'نخ', + 64590 => 'نم', + 64591 => 'نى', + 64592 => 'ني', + 64593 => 'هج', + 64594 => 'هم', + 64595 => 'هى', + 64596 => 'هي', + 64597 => 'يج', + 64598 => 'يح', + 64599 => 'يخ', + 64600 => 'يم', + 64601 => 'يى', + 64602 => 'يي', + 64603 => 'ذٰ', + 64604 => 'رٰ', + 64605 => 'ىٰ', + 64612 => 'ئر', + 64613 => 'ئز', + 64614 => 'ئم', + 64615 => 'ئن', + 64616 => 'ئى', + 64617 => 'ئي', + 64618 => 'بر', + 64619 => 'بز', + 64620 => 'بم', + 64621 => 'بن', + 64622 => 'بى', + 64623 => 'بي', + 64624 => 'تر', + 64625 => 'تز', + 64626 => 'تم', + 64627 => 'تن', + 64628 => 'تى', + 64629 => 'تي', + 64630 => 'ثر', + 64631 => 'ثز', + 64632 => 'ثم', + 64633 => 'ثن', + 64634 => 'ثى', + 64635 => 'ثي', + 64636 => 'فى', + 64637 => 'في', + 64638 => 'قى', + 64639 => 'قي', + 64640 => 'كا', + 64641 => 'كل', + 64642 => 'كم', + 64643 => 'كى', + 64644 => 'كي', + 64645 => 'لم', + 64646 => 'لى', + 64647 => 'لي', + 64648 => 'ما', + 64649 => 'مم', + 64650 => 'نر', + 64651 => 'نز', + 64652 => 'نم', + 64653 => 'نن', + 64654 => 'نى', + 64655 => 'ني', + 64656 => 'ىٰ', + 64657 => 'ير', + 64658 => 'يز', + 64659 => 'يم', + 64660 => 'ين', + 64661 => 'يى', + 64662 => 'يي', + 64663 => 'ئج', + 64664 => 'ئح', + 64665 => 'ئخ', + 64666 => 'ئم', + 64667 => 'ئه', + 64668 => 'بج', + 64669 => 'بح', + 64670 => 'بخ', + 64671 => 'بم', + 64672 => 'به', + 64673 => 'تج', + 64674 => 'تح', + 64675 => 'تخ', + 64676 => 'تم', + 64677 => 'ته', + 64678 => 'ثم', + 64679 => 'جح', + 64680 => 'جم', + 64681 => 'حج', + 64682 => 'حم', + 64683 => 'خج', + 64684 => 'خم', + 64685 => 'سج', + 64686 => 'سح', + 64687 => 'سخ', + 64688 => 'سم', + 64689 => 'صح', + 64690 => 'صخ', + 64691 => 'صم', + 64692 => 'ضج', + 64693 => 'ضح', + 64694 => 'ضخ', + 64695 => 'ضم', + 64696 => 'طح', + 64697 => 'ظم', + 64698 => 'عج', + 64699 => 'عم', + 64700 => 'غج', + 64701 => 'غم', + 64702 => 'فج', + 64703 => 'فح', + 64704 => 'فخ', + 64705 => 'فم', + 64706 => 'قح', + 64707 => 'قم', + 64708 => 'كج', + 64709 => 'كح', + 64710 => 'كخ', + 64711 => 'كل', + 64712 => 'كم', + 64713 => 'لج', + 64714 => 'لح', + 64715 => 'لخ', + 64716 => 'لم', + 64717 => 'له', + 64718 => 'مج', + 64719 => 'مح', + 64720 => 'مخ', + 64721 => 'مم', + 64722 => 'نج', + 64723 => 'نح', + 64724 => 'نخ', + 64725 => 'نم', + 64726 => 'نه', + 64727 => 'هج', + 64728 => 'هم', + 64729 => 'هٰ', + 64730 => 'يج', + 64731 => 'يح', + 64732 => 'يخ', + 64733 => 'يم', + 64734 => 'يه', + 64735 => 'ئم', + 64736 => 'ئه', + 64737 => 'بم', + 64738 => 'به', + 64739 => 'تم', + 64740 => 'ته', + 64741 => 'ثم', + 64742 => 'ثه', + 64743 => 'سم', + 64744 => 'سه', + 64745 => 'شم', + 64746 => 'شه', + 64747 => 'كل', + 64748 => 'كم', + 64749 => 'لم', + 64750 => 'نم', + 64751 => 'نه', + 64752 => 'يم', + 64753 => 'يه', + 64754 => 'ـَّ', + 64755 => 'ـُّ', + 64756 => 'ـِّ', + 64757 => 'طى', + 64758 => 'طي', + 64759 => 'عى', + 64760 => 'عي', + 64761 => 'غى', + 64762 => 'غي', + 64763 => 'سى', + 64764 => 'سي', + 64765 => 'شى', + 64766 => 'شي', + 64767 => 'حى', + 64768 => 'حي', + 64769 => 'جى', + 64770 => 'جي', + 64771 => 'خى', + 64772 => 'خي', + 64773 => 'صى', + 64774 => 'صي', + 64775 => 'ضى', + 64776 => 'ضي', + 64777 => 'شج', + 64778 => 'شح', + 64779 => 'شخ', + 64780 => 'شم', + 64781 => 'شر', + 64782 => 'سر', + 64783 => 'صر', + 64784 => 'ضر', + 64785 => 'طى', + 64786 => 'طي', + 64787 => 'عى', + 64788 => 'عي', + 64789 => 'غى', + 64790 => 'غي', + 64791 => 'سى', + 64792 => 'سي', + 64793 => 'شى', + 64794 => 'شي', + 64795 => 'حى', + 64796 => 'حي', + 64797 => 'جى', + 64798 => 'جي', + 64799 => 'خى', + 64800 => 'خي', + 64801 => 'صى', + 64802 => 'صي', + 64803 => 'ضى', + 64804 => 'ضي', + 64805 => 'شج', + 64806 => 'شح', + 64807 => 'شخ', + 64808 => 'شم', + 64809 => 'شر', + 64810 => 'سر', + 64811 => 'صر', + 64812 => 'ضر', + 64813 => 'شج', + 64814 => 'شح', + 64815 => 'شخ', + 64816 => 'شم', + 64817 => 'سه', + 64818 => 'شه', + 64819 => 'طم', + 64820 => 'سج', + 64821 => 'سح', + 64822 => 'سخ', + 64823 => 'شج', + 64824 => 'شح', + 64825 => 'شخ', + 64826 => 'طم', + 64827 => 'ظم', + 64828 => 'اً', + 64829 => 'اً', + 64848 => 'تجم', + 64849 => 'تحج', + 64850 => 'تحج', + 64851 => 'تحم', + 64852 => 'تخم', + 64853 => 'تمج', + 64854 => 'تمح', + 64855 => 'تمخ', + 64856 => 'جمح', + 64857 => 'جمح', + 64858 => 'حمي', + 64859 => 'حمى', + 64860 => 'سحج', + 64861 => 'سجح', + 64862 => 'سجى', + 64863 => 'سمح', + 64864 => 'سمح', + 64865 => 'سمج', + 64866 => 'سمم', + 64867 => 'سمم', + 64868 => 'صحح', + 64869 => 'صحح', + 64870 => 'صمم', + 64871 => 'شحم', + 64872 => 'شحم', + 64873 => 'شجي', + 64874 => 'شمخ', + 64875 => 'شمخ', + 64876 => 'شمم', + 64877 => 'شمم', + 64878 => 'ضحى', + 64879 => 'ضخم', + 64880 => 'ضخم', + 64881 => 'طمح', + 64882 => 'طمح', + 64883 => 'طمم', + 64884 => 'طمي', + 64885 => 'عجم', + 64886 => 'عمم', + 64887 => 'عمم', + 64888 => 'عمى', + 64889 => 'غمم', + 64890 => 'غمي', + 64891 => 'غمى', + 64892 => 'فخم', + 64893 => 'فخم', + 64894 => 'قمح', + 64895 => 'قمم', + 64896 => 'لحم', + 64897 => 'لحي', + 64898 => 'لحى', + 64899 => 'لجج', + 64900 => 'لجج', + 64901 => 'لخم', + 64902 => 'لخم', + 64903 => 'لمح', + 64904 => 'لمح', + 64905 => 'محج', + 64906 => 'محم', + 64907 => 'محي', + 64908 => 'مجح', + 64909 => 'مجم', + 64910 => 'مخج', + 64911 => 'مخم', + 64914 => 'مجخ', + 64915 => 'همج', + 64916 => 'همم', + 64917 => 'نحم', + 64918 => 'نحى', + 64919 => 'نجم', + 64920 => 'نجم', + 64921 => 'نجى', + 64922 => 'نمي', + 64923 => 'نمى', + 64924 => 'يمم', + 64925 => 'يمم', + 64926 => 'بخي', + 64927 => 'تجي', + 64928 => 'تجى', + 64929 => 'تخي', + 64930 => 'تخى', + 64931 => 'تمي', + 64932 => 'تمى', + 64933 => 'جمي', + 64934 => 'جحى', + 64935 => 'جمى', + 64936 => 'سخى', + 64937 => 'صحي', + 64938 => 'شحي', + 64939 => 'ضحي', + 64940 => 'لجي', + 64941 => 'لمي', + 64942 => 'يحي', + 64943 => 'يجي', + 64944 => 'يمي', + 64945 => 'ممي', + 64946 => 'قمي', + 64947 => 'نحي', + 64948 => 'قمح', + 64949 => 'لحم', + 64950 => 'عمي', + 64951 => 'كمي', + 64952 => 'نجح', + 64953 => 'مخي', + 64954 => 'لجم', + 64955 => 'كمم', + 64956 => 'لجم', + 64957 => 'نجح', + 64958 => 'جحي', + 64959 => 'حجي', + 64960 => 'مجي', + 64961 => 'فمي', + 64962 => 'بحي', + 64963 => 'كمم', + 64964 => 'عجم', + 64965 => 'صمم', + 64966 => 'سخي', + 64967 => 'نجي', + 65008 => 'صلے', + 65009 => 'قلے', + 65010 => 'الله', + 65011 => 'اكبر', + 65012 => 'محمد', + 65013 => 'صلعم', + 65014 => 'رسول', + 65015 => 'عليه', + 65016 => 'وسلم', + 65017 => 'صلى', + 65020 => 'ریال', + 65041 => '、', + 65047 => '〖', + 65048 => '〗', + 65073 => '—', + 65074 => '–', + 65081 => '〔', + 65082 => '〕', + 65083 => '【', + 65084 => '】', + 65085 => '《', + 65086 => '》', + 65087 => '〈', + 65088 => '〉', + 65089 => '「', + 65090 => '」', + 65091 => '『', + 65092 => '』', + 65105 => '、', + 65112 => '—', + 65117 => '〔', + 65118 => '〕', + 65123 => '-', + 65137 => 'ـً', + 65143 => 'ـَ', + 65145 => 'ـُ', + 65147 => 'ـِ', + 65149 => 'ـّ', + 65151 => 'ـْ', + 65152 => 'ء', + 65153 => 'آ', + 65154 => 'آ', + 65155 => 'أ', + 65156 => 'أ', + 65157 => 'ؤ', + 65158 => 'ؤ', + 65159 => 'إ', + 65160 => 'إ', + 65161 => 'ئ', + 65162 => 'ئ', + 65163 => 'ئ', + 65164 => 'ئ', + 65165 => 'ا', + 65166 => 'ا', + 65167 => 'ب', + 65168 => 'ب', + 65169 => 'ب', + 65170 => 'ب', + 65171 => 'ة', + 65172 => 'ة', + 65173 => 'ت', + 65174 => 'ت', + 65175 => 'ت', + 65176 => 'ت', + 65177 => 'ث', + 65178 => 'ث', + 65179 => 'ث', + 65180 => 'ث', + 65181 => 'ج', + 65182 => 'ج', + 65183 => 'ج', + 65184 => 'ج', + 65185 => 'ح', + 65186 => 'ح', + 65187 => 'ح', + 65188 => 'ح', + 65189 => 'خ', + 65190 => 'خ', + 65191 => 'خ', + 65192 => 'خ', + 65193 => 'د', + 65194 => 'د', + 65195 => 'ذ', + 65196 => 'ذ', + 65197 => 'ر', + 65198 => 'ر', + 65199 => 'ز', + 65200 => 'ز', + 65201 => 'س', + 65202 => 'س', + 65203 => 'س', + 65204 => 'س', + 65205 => 'ش', + 65206 => 'ش', + 65207 => 'ش', + 65208 => 'ش', + 65209 => 'ص', + 65210 => 'ص', + 65211 => 'ص', + 65212 => 'ص', + 65213 => 'ض', + 65214 => 'ض', + 65215 => 'ض', + 65216 => 'ض', + 65217 => 'ط', + 65218 => 'ط', + 65219 => 'ط', + 65220 => 'ط', + 65221 => 'ظ', + 65222 => 'ظ', + 65223 => 'ظ', + 65224 => 'ظ', + 65225 => 'ع', + 65226 => 'ع', + 65227 => 'ع', + 65228 => 'ع', + 65229 => 'غ', + 65230 => 'غ', + 65231 => 'غ', + 65232 => 'غ', + 65233 => 'ف', + 65234 => 'ف', + 65235 => 'ف', + 65236 => 'ف', + 65237 => 'ق', + 65238 => 'ق', + 65239 => 'ق', + 65240 => 'ق', + 65241 => 'ك', + 65242 => 'ك', + 65243 => 'ك', + 65244 => 'ك', + 65245 => 'ل', + 65246 => 'ل', + 65247 => 'ل', + 65248 => 'ل', + 65249 => 'م', + 65250 => 'م', + 65251 => 'م', + 65252 => 'م', + 65253 => 'ن', + 65254 => 'ن', + 65255 => 'ن', + 65256 => 'ن', + 65257 => 'ه', + 65258 => 'ه', + 65259 => 'ه', + 65260 => 'ه', + 65261 => 'و', + 65262 => 'و', + 65263 => 'ى', + 65264 => 'ى', + 65265 => 'ي', + 65266 => 'ي', + 65267 => 'ي', + 65268 => 'ي', + 65269 => 'لآ', + 65270 => 'لآ', + 65271 => 'لأ', + 65272 => 'لأ', + 65273 => 'لإ', + 65274 => 'لإ', + 65275 => 'لا', + 65276 => 'لا', + 65293 => '-', + 65294 => '.', + 65296 => '0', + 65297 => '1', + 65298 => '2', + 65299 => '3', + 65300 => '4', + 65301 => '5', + 65302 => '6', + 65303 => '7', + 65304 => '8', + 65305 => '9', + 65313 => 'a', + 65314 => 'b', + 65315 => 'c', + 65316 => 'd', + 65317 => 'e', + 65318 => 'f', + 65319 => 'g', + 65320 => 'h', + 65321 => 'i', + 65322 => 'j', + 65323 => 'k', + 65324 => 'l', + 65325 => 'm', + 65326 => 'n', + 65327 => 'o', + 65328 => 'p', + 65329 => 'q', + 65330 => 'r', + 65331 => 's', + 65332 => 't', + 65333 => 'u', + 65334 => 'v', + 65335 => 'w', + 65336 => 'x', + 65337 => 'y', + 65338 => 'z', + 65345 => 'a', + 65346 => 'b', + 65347 => 'c', + 65348 => 'd', + 65349 => 'e', + 65350 => 'f', + 65351 => 'g', + 65352 => 'h', + 65353 => 'i', + 65354 => 'j', + 65355 => 'k', + 65356 => 'l', + 65357 => 'm', + 65358 => 'n', + 65359 => 'o', + 65360 => 'p', + 65361 => 'q', + 65362 => 'r', + 65363 => 's', + 65364 => 't', + 65365 => 'u', + 65366 => 'v', + 65367 => 'w', + 65368 => 'x', + 65369 => 'y', + 65370 => 'z', + 65375 => '⦅', + 65376 => '⦆', + 65377 => '.', + 65378 => '「', + 65379 => '」', + 65380 => '、', + 65381 => '・', + 65382 => 'ヲ', + 65383 => 'ァ', + 65384 => 'ィ', + 65385 => 'ゥ', + 65386 => 'ェ', + 65387 => 'ォ', + 65388 => 'ャ', + 65389 => 'ュ', + 65390 => 'ョ', + 65391 => 'ッ', + 65392 => 'ー', + 65393 => 'ア', + 65394 => 'イ', + 65395 => 'ウ', + 65396 => 'エ', + 65397 => 'オ', + 65398 => 'カ', + 65399 => 'キ', + 65400 => 'ク', + 65401 => 'ケ', + 65402 => 'コ', + 65403 => 'サ', + 65404 => 'シ', + 65405 => 'ス', + 65406 => 'セ', + 65407 => 'ソ', + 65408 => 'タ', + 65409 => 'チ', + 65410 => 'ツ', + 65411 => 'テ', + 65412 => 'ト', + 65413 => 'ナ', + 65414 => 'ニ', + 65415 => 'ヌ', + 65416 => 'ネ', + 65417 => 'ノ', + 65418 => 'ハ', + 65419 => 'ヒ', + 65420 => 'フ', + 65421 => 'ヘ', + 65422 => 'ホ', + 65423 => 'マ', + 65424 => 'ミ', + 65425 => 'ム', + 65426 => 'メ', + 65427 => 'モ', + 65428 => 'ヤ', + 65429 => 'ユ', + 65430 => 'ヨ', + 65431 => 'ラ', + 65432 => 'リ', + 65433 => 'ル', + 65434 => 'レ', + 65435 => 'ロ', + 65436 => 'ワ', + 65437 => 'ン', + 65438 => '゙', + 65439 => '゚', + 65441 => 'ᄀ', + 65442 => 'ᄁ', + 65443 => 'ᆪ', + 65444 => 'ᄂ', + 65445 => 'ᆬ', + 65446 => 'ᆭ', + 65447 => 'ᄃ', + 65448 => 'ᄄ', + 65449 => 'ᄅ', + 65450 => 'ᆰ', + 65451 => 'ᆱ', + 65452 => 'ᆲ', + 65453 => 'ᆳ', + 65454 => 'ᆴ', + 65455 => 'ᆵ', + 65456 => 'ᄚ', + 65457 => 'ᄆ', + 65458 => 'ᄇ', + 65459 => 'ᄈ', + 65460 => 'ᄡ', + 65461 => 'ᄉ', + 65462 => 'ᄊ', + 65463 => 'ᄋ', + 65464 => 'ᄌ', + 65465 => 'ᄍ', + 65466 => 'ᄎ', + 65467 => 'ᄏ', + 65468 => 'ᄐ', + 65469 => 'ᄑ', + 65470 => 'ᄒ', + 65474 => 'ᅡ', + 65475 => 'ᅢ', + 65476 => 'ᅣ', + 65477 => 'ᅤ', + 65478 => 'ᅥ', + 65479 => 'ᅦ', + 65482 => 'ᅧ', + 65483 => 'ᅨ', + 65484 => 'ᅩ', + 65485 => 'ᅪ', + 65486 => 'ᅫ', + 65487 => 'ᅬ', + 65490 => 'ᅭ', + 65491 => 'ᅮ', + 65492 => 'ᅯ', + 65493 => 'ᅰ', + 65494 => 'ᅱ', + 65495 => 'ᅲ', + 65498 => 'ᅳ', + 65499 => 'ᅴ', + 65500 => 'ᅵ', + 65504 => '¢', + 65505 => '£', + 65506 => '¬', + 65508 => '¦', + 65509 => '¥', + 65510 => '₩', + 65512 => '│', + 65513 => '←', + 65514 => '↑', + 65515 => '→', + 65516 => '↓', + 65517 => '■', + 65518 => '○', + 66560 => '𐐨', + 66561 => '𐐩', + 66562 => '𐐪', + 66563 => '𐐫', + 66564 => '𐐬', + 66565 => '𐐭', + 66566 => '𐐮', + 66567 => '𐐯', + 66568 => '𐐰', + 66569 => '𐐱', + 66570 => '𐐲', + 66571 => '𐐳', + 66572 => '𐐴', + 66573 => '𐐵', + 66574 => '𐐶', + 66575 => '𐐷', + 66576 => '𐐸', + 66577 => '𐐹', + 66578 => '𐐺', + 66579 => '𐐻', + 66580 => '𐐼', + 66581 => '𐐽', + 66582 => '𐐾', + 66583 => '𐐿', + 66584 => '𐑀', + 66585 => '𐑁', + 66586 => '𐑂', + 66587 => '𐑃', + 66588 => '𐑄', + 66589 => '𐑅', + 66590 => '𐑆', + 66591 => '𐑇', + 66592 => '𐑈', + 66593 => '𐑉', + 66594 => '𐑊', + 66595 => '𐑋', + 66596 => '𐑌', + 66597 => '𐑍', + 66598 => '𐑎', + 66599 => '𐑏', + 66736 => '𐓘', + 66737 => '𐓙', + 66738 => '𐓚', + 66739 => '𐓛', + 66740 => '𐓜', + 66741 => '𐓝', + 66742 => '𐓞', + 66743 => '𐓟', + 66744 => '𐓠', + 66745 => '𐓡', + 66746 => '𐓢', + 66747 => '𐓣', + 66748 => '𐓤', + 66749 => '𐓥', + 66750 => '𐓦', + 66751 => '𐓧', + 66752 => '𐓨', + 66753 => '𐓩', + 66754 => '𐓪', + 66755 => '𐓫', + 66756 => '𐓬', + 66757 => '𐓭', + 66758 => '𐓮', + 66759 => '𐓯', + 66760 => '𐓰', + 66761 => '𐓱', + 66762 => '𐓲', + 66763 => '𐓳', + 66764 => '𐓴', + 66765 => '𐓵', + 66766 => '𐓶', + 66767 => '𐓷', + 66768 => '𐓸', + 66769 => '𐓹', + 66770 => '𐓺', + 66771 => '𐓻', + 68736 => '𐳀', + 68737 => '𐳁', + 68738 => '𐳂', + 68739 => '𐳃', + 68740 => '𐳄', + 68741 => '𐳅', + 68742 => '𐳆', + 68743 => '𐳇', + 68744 => '𐳈', + 68745 => '𐳉', + 68746 => '𐳊', + 68747 => '𐳋', + 68748 => '𐳌', + 68749 => '𐳍', + 68750 => '𐳎', + 68751 => '𐳏', + 68752 => '𐳐', + 68753 => '𐳑', + 68754 => '𐳒', + 68755 => '𐳓', + 68756 => '𐳔', + 68757 => '𐳕', + 68758 => '𐳖', + 68759 => '𐳗', + 68760 => '𐳘', + 68761 => '𐳙', + 68762 => '𐳚', + 68763 => '𐳛', + 68764 => '𐳜', + 68765 => '𐳝', + 68766 => '𐳞', + 68767 => '𐳟', + 68768 => '𐳠', + 68769 => '𐳡', + 68770 => '𐳢', + 68771 => '𐳣', + 68772 => '𐳤', + 68773 => '𐳥', + 68774 => '𐳦', + 68775 => '𐳧', + 68776 => '𐳨', + 68777 => '𐳩', + 68778 => '𐳪', + 68779 => '𐳫', + 68780 => '𐳬', + 68781 => '𐳭', + 68782 => '𐳮', + 68783 => '𐳯', + 68784 => '𐳰', + 68785 => '𐳱', + 68786 => '𐳲', + 71840 => '𑣀', + 71841 => '𑣁', + 71842 => '𑣂', + 71843 => '𑣃', + 71844 => '𑣄', + 71845 => '𑣅', + 71846 => '𑣆', + 71847 => '𑣇', + 71848 => '𑣈', + 71849 => '𑣉', + 71850 => '𑣊', + 71851 => '𑣋', + 71852 => '𑣌', + 71853 => '𑣍', + 71854 => '𑣎', + 71855 => '𑣏', + 71856 => '𑣐', + 71857 => '𑣑', + 71858 => '𑣒', + 71859 => '𑣓', + 71860 => '𑣔', + 71861 => '𑣕', + 71862 => '𑣖', + 71863 => '𑣗', + 71864 => '𑣘', + 71865 => '𑣙', + 71866 => '𑣚', + 71867 => '𑣛', + 71868 => '𑣜', + 71869 => '𑣝', + 71870 => '𑣞', + 71871 => '𑣟', + 93760 => '𖹠', + 93761 => '𖹡', + 93762 => '𖹢', + 93763 => '𖹣', + 93764 => '𖹤', + 93765 => '𖹥', + 93766 => '𖹦', + 93767 => '𖹧', + 93768 => '𖹨', + 93769 => '𖹩', + 93770 => '𖹪', + 93771 => '𖹫', + 93772 => '𖹬', + 93773 => '𖹭', + 93774 => '𖹮', + 93775 => '𖹯', + 93776 => '𖹰', + 93777 => '𖹱', + 93778 => '𖹲', + 93779 => '𖹳', + 93780 => '𖹴', + 93781 => '𖹵', + 93782 => '𖹶', + 93783 => '𖹷', + 93784 => '𖹸', + 93785 => '𖹹', + 93786 => '𖹺', + 93787 => '𖹻', + 93788 => '𖹼', + 93789 => '𖹽', + 93790 => '𖹾', + 93791 => '𖹿', + 119134 => '𝅗𝅥', + 119135 => '𝅘𝅥', + 119136 => '𝅘𝅥𝅮', + 119137 => '𝅘𝅥𝅯', + 119138 => '𝅘𝅥𝅰', + 119139 => '𝅘𝅥𝅱', + 119140 => '𝅘𝅥𝅲', + 119227 => '𝆹𝅥', + 119228 => '𝆺𝅥', + 119229 => '𝆹𝅥𝅮', + 119230 => '𝆺𝅥𝅮', + 119231 => '𝆹𝅥𝅯', + 119232 => '𝆺𝅥𝅯', + 119808 => 'a', + 119809 => 'b', + 119810 => 'c', + 119811 => 'd', + 119812 => 'e', + 119813 => 'f', + 119814 => 'g', + 119815 => 'h', + 119816 => 'i', + 119817 => 'j', + 119818 => 'k', + 119819 => 'l', + 119820 => 'm', + 119821 => 'n', + 119822 => 'o', + 119823 => 'p', + 119824 => 'q', + 119825 => 'r', + 119826 => 's', + 119827 => 't', + 119828 => 'u', + 119829 => 'v', + 119830 => 'w', + 119831 => 'x', + 119832 => 'y', + 119833 => 'z', + 119834 => 'a', + 119835 => 'b', + 119836 => 'c', + 119837 => 'd', + 119838 => 'e', + 119839 => 'f', + 119840 => 'g', + 119841 => 'h', + 119842 => 'i', + 119843 => 'j', + 119844 => 'k', + 119845 => 'l', + 119846 => 'm', + 119847 => 'n', + 119848 => 'o', + 119849 => 'p', + 119850 => 'q', + 119851 => 'r', + 119852 => 's', + 119853 => 't', + 119854 => 'u', + 119855 => 'v', + 119856 => 'w', + 119857 => 'x', + 119858 => 'y', + 119859 => 'z', + 119860 => 'a', + 119861 => 'b', + 119862 => 'c', + 119863 => 'd', + 119864 => 'e', + 119865 => 'f', + 119866 => 'g', + 119867 => 'h', + 119868 => 'i', + 119869 => 'j', + 119870 => 'k', + 119871 => 'l', + 119872 => 'm', + 119873 => 'n', + 119874 => 'o', + 119875 => 'p', + 119876 => 'q', + 119877 => 'r', + 119878 => 's', + 119879 => 't', + 119880 => 'u', + 119881 => 'v', + 119882 => 'w', + 119883 => 'x', + 119884 => 'y', + 119885 => 'z', + 119886 => 'a', + 119887 => 'b', + 119888 => 'c', + 119889 => 'd', + 119890 => 'e', + 119891 => 'f', + 119892 => 'g', + 119894 => 'i', + 119895 => 'j', + 119896 => 'k', + 119897 => 'l', + 119898 => 'm', + 119899 => 'n', + 119900 => 'o', + 119901 => 'p', + 119902 => 'q', + 119903 => 'r', + 119904 => 's', + 119905 => 't', + 119906 => 'u', + 119907 => 'v', + 119908 => 'w', + 119909 => 'x', + 119910 => 'y', + 119911 => 'z', + 119912 => 'a', + 119913 => 'b', + 119914 => 'c', + 119915 => 'd', + 119916 => 'e', + 119917 => 'f', + 119918 => 'g', + 119919 => 'h', + 119920 => 'i', + 119921 => 'j', + 119922 => 'k', + 119923 => 'l', + 119924 => 'm', + 119925 => 'n', + 119926 => 'o', + 119927 => 'p', + 119928 => 'q', + 119929 => 'r', + 119930 => 's', + 119931 => 't', + 119932 => 'u', + 119933 => 'v', + 119934 => 'w', + 119935 => 'x', + 119936 => 'y', + 119937 => 'z', + 119938 => 'a', + 119939 => 'b', + 119940 => 'c', + 119941 => 'd', + 119942 => 'e', + 119943 => 'f', + 119944 => 'g', + 119945 => 'h', + 119946 => 'i', + 119947 => 'j', + 119948 => 'k', + 119949 => 'l', + 119950 => 'm', + 119951 => 'n', + 119952 => 'o', + 119953 => 'p', + 119954 => 'q', + 119955 => 'r', + 119956 => 's', + 119957 => 't', + 119958 => 'u', + 119959 => 'v', + 119960 => 'w', + 119961 => 'x', + 119962 => 'y', + 119963 => 'z', + 119964 => 'a', + 119966 => 'c', + 119967 => 'd', + 119970 => 'g', + 119973 => 'j', + 119974 => 'k', + 119977 => 'n', + 119978 => 'o', + 119979 => 'p', + 119980 => 'q', + 119982 => 's', + 119983 => 't', + 119984 => 'u', + 119985 => 'v', + 119986 => 'w', + 119987 => 'x', + 119988 => 'y', + 119989 => 'z', + 119990 => 'a', + 119991 => 'b', + 119992 => 'c', + 119993 => 'd', + 119995 => 'f', + 119997 => 'h', + 119998 => 'i', + 119999 => 'j', + 120000 => 'k', + 120001 => 'l', + 120002 => 'm', + 120003 => 'n', + 120005 => 'p', + 120006 => 'q', + 120007 => 'r', + 120008 => 's', + 120009 => 't', + 120010 => 'u', + 120011 => 'v', + 120012 => 'w', + 120013 => 'x', + 120014 => 'y', + 120015 => 'z', + 120016 => 'a', + 120017 => 'b', + 120018 => 'c', + 120019 => 'd', + 120020 => 'e', + 120021 => 'f', + 120022 => 'g', + 120023 => 'h', + 120024 => 'i', + 120025 => 'j', + 120026 => 'k', + 120027 => 'l', + 120028 => 'm', + 120029 => 'n', + 120030 => 'o', + 120031 => 'p', + 120032 => 'q', + 120033 => 'r', + 120034 => 's', + 120035 => 't', + 120036 => 'u', + 120037 => 'v', + 120038 => 'w', + 120039 => 'x', + 120040 => 'y', + 120041 => 'z', + 120042 => 'a', + 120043 => 'b', + 120044 => 'c', + 120045 => 'd', + 120046 => 'e', + 120047 => 'f', + 120048 => 'g', + 120049 => 'h', + 120050 => 'i', + 120051 => 'j', + 120052 => 'k', + 120053 => 'l', + 120054 => 'm', + 120055 => 'n', + 120056 => 'o', + 120057 => 'p', + 120058 => 'q', + 120059 => 'r', + 120060 => 's', + 120061 => 't', + 120062 => 'u', + 120063 => 'v', + 120064 => 'w', + 120065 => 'x', + 120066 => 'y', + 120067 => 'z', + 120068 => 'a', + 120069 => 'b', + 120071 => 'd', + 120072 => 'e', + 120073 => 'f', + 120074 => 'g', + 120077 => 'j', + 120078 => 'k', + 120079 => 'l', + 120080 => 'm', + 120081 => 'n', + 120082 => 'o', + 120083 => 'p', + 120084 => 'q', + 120086 => 's', + 120087 => 't', + 120088 => 'u', + 120089 => 'v', + 120090 => 'w', + 120091 => 'x', + 120092 => 'y', + 120094 => 'a', + 120095 => 'b', + 120096 => 'c', + 120097 => 'd', + 120098 => 'e', + 120099 => 'f', + 120100 => 'g', + 120101 => 'h', + 120102 => 'i', + 120103 => 'j', + 120104 => 'k', + 120105 => 'l', + 120106 => 'm', + 120107 => 'n', + 120108 => 'o', + 120109 => 'p', + 120110 => 'q', + 120111 => 'r', + 120112 => 's', + 120113 => 't', + 120114 => 'u', + 120115 => 'v', + 120116 => 'w', + 120117 => 'x', + 120118 => 'y', + 120119 => 'z', + 120120 => 'a', + 120121 => 'b', + 120123 => 'd', + 120124 => 'e', + 120125 => 'f', + 120126 => 'g', + 120128 => 'i', + 120129 => 'j', + 120130 => 'k', + 120131 => 'l', + 120132 => 'm', + 120134 => 'o', + 120138 => 's', + 120139 => 't', + 120140 => 'u', + 120141 => 'v', + 120142 => 'w', + 120143 => 'x', + 120144 => 'y', + 120146 => 'a', + 120147 => 'b', + 120148 => 'c', + 120149 => 'd', + 120150 => 'e', + 120151 => 'f', + 120152 => 'g', + 120153 => 'h', + 120154 => 'i', + 120155 => 'j', + 120156 => 'k', + 120157 => 'l', + 120158 => 'm', + 120159 => 'n', + 120160 => 'o', + 120161 => 'p', + 120162 => 'q', + 120163 => 'r', + 120164 => 's', + 120165 => 't', + 120166 => 'u', + 120167 => 'v', + 120168 => 'w', + 120169 => 'x', + 120170 => 'y', + 120171 => 'z', + 120172 => 'a', + 120173 => 'b', + 120174 => 'c', + 120175 => 'd', + 120176 => 'e', + 120177 => 'f', + 120178 => 'g', + 120179 => 'h', + 120180 => 'i', + 120181 => 'j', + 120182 => 'k', + 120183 => 'l', + 120184 => 'm', + 120185 => 'n', + 120186 => 'o', + 120187 => 'p', + 120188 => 'q', + 120189 => 'r', + 120190 => 's', + 120191 => 't', + 120192 => 'u', + 120193 => 'v', + 120194 => 'w', + 120195 => 'x', + 120196 => 'y', + 120197 => 'z', + 120198 => 'a', + 120199 => 'b', + 120200 => 'c', + 120201 => 'd', + 120202 => 'e', + 120203 => 'f', + 120204 => 'g', + 120205 => 'h', + 120206 => 'i', + 120207 => 'j', + 120208 => 'k', + 120209 => 'l', + 120210 => 'm', + 120211 => 'n', + 120212 => 'o', + 120213 => 'p', + 120214 => 'q', + 120215 => 'r', + 120216 => 's', + 120217 => 't', + 120218 => 'u', + 120219 => 'v', + 120220 => 'w', + 120221 => 'x', + 120222 => 'y', + 120223 => 'z', + 120224 => 'a', + 120225 => 'b', + 120226 => 'c', + 120227 => 'd', + 120228 => 'e', + 120229 => 'f', + 120230 => 'g', + 120231 => 'h', + 120232 => 'i', + 120233 => 'j', + 120234 => 'k', + 120235 => 'l', + 120236 => 'm', + 120237 => 'n', + 120238 => 'o', + 120239 => 'p', + 120240 => 'q', + 120241 => 'r', + 120242 => 's', + 120243 => 't', + 120244 => 'u', + 120245 => 'v', + 120246 => 'w', + 120247 => 'x', + 120248 => 'y', + 120249 => 'z', + 120250 => 'a', + 120251 => 'b', + 120252 => 'c', + 120253 => 'd', + 120254 => 'e', + 120255 => 'f', + 120256 => 'g', + 120257 => 'h', + 120258 => 'i', + 120259 => 'j', + 120260 => 'k', + 120261 => 'l', + 120262 => 'm', + 120263 => 'n', + 120264 => 'o', + 120265 => 'p', + 120266 => 'q', + 120267 => 'r', + 120268 => 's', + 120269 => 't', + 120270 => 'u', + 120271 => 'v', + 120272 => 'w', + 120273 => 'x', + 120274 => 'y', + 120275 => 'z', + 120276 => 'a', + 120277 => 'b', + 120278 => 'c', + 120279 => 'd', + 120280 => 'e', + 120281 => 'f', + 120282 => 'g', + 120283 => 'h', + 120284 => 'i', + 120285 => 'j', + 120286 => 'k', + 120287 => 'l', + 120288 => 'm', + 120289 => 'n', + 120290 => 'o', + 120291 => 'p', + 120292 => 'q', + 120293 => 'r', + 120294 => 's', + 120295 => 't', + 120296 => 'u', + 120297 => 'v', + 120298 => 'w', + 120299 => 'x', + 120300 => 'y', + 120301 => 'z', + 120302 => 'a', + 120303 => 'b', + 120304 => 'c', + 120305 => 'd', + 120306 => 'e', + 120307 => 'f', + 120308 => 'g', + 120309 => 'h', + 120310 => 'i', + 120311 => 'j', + 120312 => 'k', + 120313 => 'l', + 120314 => 'm', + 120315 => 'n', + 120316 => 'o', + 120317 => 'p', + 120318 => 'q', + 120319 => 'r', + 120320 => 's', + 120321 => 't', + 120322 => 'u', + 120323 => 'v', + 120324 => 'w', + 120325 => 'x', + 120326 => 'y', + 120327 => 'z', + 120328 => 'a', + 120329 => 'b', + 120330 => 'c', + 120331 => 'd', + 120332 => 'e', + 120333 => 'f', + 120334 => 'g', + 120335 => 'h', + 120336 => 'i', + 120337 => 'j', + 120338 => 'k', + 120339 => 'l', + 120340 => 'm', + 120341 => 'n', + 120342 => 'o', + 120343 => 'p', + 120344 => 'q', + 120345 => 'r', + 120346 => 's', + 120347 => 't', + 120348 => 'u', + 120349 => 'v', + 120350 => 'w', + 120351 => 'x', + 120352 => 'y', + 120353 => 'z', + 120354 => 'a', + 120355 => 'b', + 120356 => 'c', + 120357 => 'd', + 120358 => 'e', + 120359 => 'f', + 120360 => 'g', + 120361 => 'h', + 120362 => 'i', + 120363 => 'j', + 120364 => 'k', + 120365 => 'l', + 120366 => 'm', + 120367 => 'n', + 120368 => 'o', + 120369 => 'p', + 120370 => 'q', + 120371 => 'r', + 120372 => 's', + 120373 => 't', + 120374 => 'u', + 120375 => 'v', + 120376 => 'w', + 120377 => 'x', + 120378 => 'y', + 120379 => 'z', + 120380 => 'a', + 120381 => 'b', + 120382 => 'c', + 120383 => 'd', + 120384 => 'e', + 120385 => 'f', + 120386 => 'g', + 120387 => 'h', + 120388 => 'i', + 120389 => 'j', + 120390 => 'k', + 120391 => 'l', + 120392 => 'm', + 120393 => 'n', + 120394 => 'o', + 120395 => 'p', + 120396 => 'q', + 120397 => 'r', + 120398 => 's', + 120399 => 't', + 120400 => 'u', + 120401 => 'v', + 120402 => 'w', + 120403 => 'x', + 120404 => 'y', + 120405 => 'z', + 120406 => 'a', + 120407 => 'b', + 120408 => 'c', + 120409 => 'd', + 120410 => 'e', + 120411 => 'f', + 120412 => 'g', + 120413 => 'h', + 120414 => 'i', + 120415 => 'j', + 120416 => 'k', + 120417 => 'l', + 120418 => 'm', + 120419 => 'n', + 120420 => 'o', + 120421 => 'p', + 120422 => 'q', + 120423 => 'r', + 120424 => 's', + 120425 => 't', + 120426 => 'u', + 120427 => 'v', + 120428 => 'w', + 120429 => 'x', + 120430 => 'y', + 120431 => 'z', + 120432 => 'a', + 120433 => 'b', + 120434 => 'c', + 120435 => 'd', + 120436 => 'e', + 120437 => 'f', + 120438 => 'g', + 120439 => 'h', + 120440 => 'i', + 120441 => 'j', + 120442 => 'k', + 120443 => 'l', + 120444 => 'm', + 120445 => 'n', + 120446 => 'o', + 120447 => 'p', + 120448 => 'q', + 120449 => 'r', + 120450 => 's', + 120451 => 't', + 120452 => 'u', + 120453 => 'v', + 120454 => 'w', + 120455 => 'x', + 120456 => 'y', + 120457 => 'z', + 120458 => 'a', + 120459 => 'b', + 120460 => 'c', + 120461 => 'd', + 120462 => 'e', + 120463 => 'f', + 120464 => 'g', + 120465 => 'h', + 120466 => 'i', + 120467 => 'j', + 120468 => 'k', + 120469 => 'l', + 120470 => 'm', + 120471 => 'n', + 120472 => 'o', + 120473 => 'p', + 120474 => 'q', + 120475 => 'r', + 120476 => 's', + 120477 => 't', + 120478 => 'u', + 120479 => 'v', + 120480 => 'w', + 120481 => 'x', + 120482 => 'y', + 120483 => 'z', + 120484 => 'ı', + 120485 => 'ȷ', + 120488 => 'α', + 120489 => 'β', + 120490 => 'γ', + 120491 => 'δ', + 120492 => 'ε', + 120493 => 'ζ', + 120494 => 'η', + 120495 => 'θ', + 120496 => 'ι', + 120497 => 'κ', + 120498 => 'λ', + 120499 => 'μ', + 120500 => 'ν', + 120501 => 'ξ', + 120502 => 'ο', + 120503 => 'π', + 120504 => 'ρ', + 120505 => 'θ', + 120506 => 'σ', + 120507 => 'τ', + 120508 => 'υ', + 120509 => 'φ', + 120510 => 'χ', + 120511 => 'ψ', + 120512 => 'ω', + 120513 => '∇', + 120514 => 'α', + 120515 => 'β', + 120516 => 'γ', + 120517 => 'δ', + 120518 => 'ε', + 120519 => 'ζ', + 120520 => 'η', + 120521 => 'θ', + 120522 => 'ι', + 120523 => 'κ', + 120524 => 'λ', + 120525 => 'μ', + 120526 => 'ν', + 120527 => 'ξ', + 120528 => 'ο', + 120529 => 'π', + 120530 => 'ρ', + 120531 => 'σ', + 120532 => 'σ', + 120533 => 'τ', + 120534 => 'υ', + 120535 => 'φ', + 120536 => 'χ', + 120537 => 'ψ', + 120538 => 'ω', + 120539 => '∂', + 120540 => 'ε', + 120541 => 'θ', + 120542 => 'κ', + 120543 => 'φ', + 120544 => 'ρ', + 120545 => 'π', + 120546 => 'α', + 120547 => 'β', + 120548 => 'γ', + 120549 => 'δ', + 120550 => 'ε', + 120551 => 'ζ', + 120552 => 'η', + 120553 => 'θ', + 120554 => 'ι', + 120555 => 'κ', + 120556 => 'λ', + 120557 => 'μ', + 120558 => 'ν', + 120559 => 'ξ', + 120560 => 'ο', + 120561 => 'π', + 120562 => 'ρ', + 120563 => 'θ', + 120564 => 'σ', + 120565 => 'τ', + 120566 => 'υ', + 120567 => 'φ', + 120568 => 'χ', + 120569 => 'ψ', + 120570 => 'ω', + 120571 => '∇', + 120572 => 'α', + 120573 => 'β', + 120574 => 'γ', + 120575 => 'δ', + 120576 => 'ε', + 120577 => 'ζ', + 120578 => 'η', + 120579 => 'θ', + 120580 => 'ι', + 120581 => 'κ', + 120582 => 'λ', + 120583 => 'μ', + 120584 => 'ν', + 120585 => 'ξ', + 120586 => 'ο', + 120587 => 'π', + 120588 => 'ρ', + 120589 => 'σ', + 120590 => 'σ', + 120591 => 'τ', + 120592 => 'υ', + 120593 => 'φ', + 120594 => 'χ', + 120595 => 'ψ', + 120596 => 'ω', + 120597 => '∂', + 120598 => 'ε', + 120599 => 'θ', + 120600 => 'κ', + 120601 => 'φ', + 120602 => 'ρ', + 120603 => 'π', + 120604 => 'α', + 120605 => 'β', + 120606 => 'γ', + 120607 => 'δ', + 120608 => 'ε', + 120609 => 'ζ', + 120610 => 'η', + 120611 => 'θ', + 120612 => 'ι', + 120613 => 'κ', + 120614 => 'λ', + 120615 => 'μ', + 120616 => 'ν', + 120617 => 'ξ', + 120618 => 'ο', + 120619 => 'π', + 120620 => 'ρ', + 120621 => 'θ', + 120622 => 'σ', + 120623 => 'τ', + 120624 => 'υ', + 120625 => 'φ', + 120626 => 'χ', + 120627 => 'ψ', + 120628 => 'ω', + 120629 => '∇', + 120630 => 'α', + 120631 => 'β', + 120632 => 'γ', + 120633 => 'δ', + 120634 => 'ε', + 120635 => 'ζ', + 120636 => 'η', + 120637 => 'θ', + 120638 => 'ι', + 120639 => 'κ', + 120640 => 'λ', + 120641 => 'μ', + 120642 => 'ν', + 120643 => 'ξ', + 120644 => 'ο', + 120645 => 'π', + 120646 => 'ρ', + 120647 => 'σ', + 120648 => 'σ', + 120649 => 'τ', + 120650 => 'υ', + 120651 => 'φ', + 120652 => 'χ', + 120653 => 'ψ', + 120654 => 'ω', + 120655 => '∂', + 120656 => 'ε', + 120657 => 'θ', + 120658 => 'κ', + 120659 => 'φ', + 120660 => 'ρ', + 120661 => 'π', + 120662 => 'α', + 120663 => 'β', + 120664 => 'γ', + 120665 => 'δ', + 120666 => 'ε', + 120667 => 'ζ', + 120668 => 'η', + 120669 => 'θ', + 120670 => 'ι', + 120671 => 'κ', + 120672 => 'λ', + 120673 => 'μ', + 120674 => 'ν', + 120675 => 'ξ', + 120676 => 'ο', + 120677 => 'π', + 120678 => 'ρ', + 120679 => 'θ', + 120680 => 'σ', + 120681 => 'τ', + 120682 => 'υ', + 120683 => 'φ', + 120684 => 'χ', + 120685 => 'ψ', + 120686 => 'ω', + 120687 => '∇', + 120688 => 'α', + 120689 => 'β', + 120690 => 'γ', + 120691 => 'δ', + 120692 => 'ε', + 120693 => 'ζ', + 120694 => 'η', + 120695 => 'θ', + 120696 => 'ι', + 120697 => 'κ', + 120698 => 'λ', + 120699 => 'μ', + 120700 => 'ν', + 120701 => 'ξ', + 120702 => 'ο', + 120703 => 'π', + 120704 => 'ρ', + 120705 => 'σ', + 120706 => 'σ', + 120707 => 'τ', + 120708 => 'υ', + 120709 => 'φ', + 120710 => 'χ', + 120711 => 'ψ', + 120712 => 'ω', + 120713 => '∂', + 120714 => 'ε', + 120715 => 'θ', + 120716 => 'κ', + 120717 => 'φ', + 120718 => 'ρ', + 120719 => 'π', + 120720 => 'α', + 120721 => 'β', + 120722 => 'γ', + 120723 => 'δ', + 120724 => 'ε', + 120725 => 'ζ', + 120726 => 'η', + 120727 => 'θ', + 120728 => 'ι', + 120729 => 'κ', + 120730 => 'λ', + 120731 => 'μ', + 120732 => 'ν', + 120733 => 'ξ', + 120734 => 'ο', + 120735 => 'π', + 120736 => 'ρ', + 120737 => 'θ', + 120738 => 'σ', + 120739 => 'τ', + 120740 => 'υ', + 120741 => 'φ', + 120742 => 'χ', + 120743 => 'ψ', + 120744 => 'ω', + 120745 => '∇', + 120746 => 'α', + 120747 => 'β', + 120748 => 'γ', + 120749 => 'δ', + 120750 => 'ε', + 120751 => 'ζ', + 120752 => 'η', + 120753 => 'θ', + 120754 => 'ι', + 120755 => 'κ', + 120756 => 'λ', + 120757 => 'μ', + 120758 => 'ν', + 120759 => 'ξ', + 120760 => 'ο', + 120761 => 'π', + 120762 => 'ρ', + 120763 => 'σ', + 120764 => 'σ', + 120765 => 'τ', + 120766 => 'υ', + 120767 => 'φ', + 120768 => 'χ', + 120769 => 'ψ', + 120770 => 'ω', + 120771 => '∂', + 120772 => 'ε', + 120773 => 'θ', + 120774 => 'κ', + 120775 => 'φ', + 120776 => 'ρ', + 120777 => 'π', + 120778 => 'ϝ', + 120779 => 'ϝ', + 120782 => '0', + 120783 => '1', + 120784 => '2', + 120785 => '3', + 120786 => '4', + 120787 => '5', + 120788 => '6', + 120789 => '7', + 120790 => '8', + 120791 => '9', + 120792 => '0', + 120793 => '1', + 120794 => '2', + 120795 => '3', + 120796 => '4', + 120797 => '5', + 120798 => '6', + 120799 => '7', + 120800 => '8', + 120801 => '9', + 120802 => '0', + 120803 => '1', + 120804 => '2', + 120805 => '3', + 120806 => '4', + 120807 => '5', + 120808 => '6', + 120809 => '7', + 120810 => '8', + 120811 => '9', + 120812 => '0', + 120813 => '1', + 120814 => '2', + 120815 => '3', + 120816 => '4', + 120817 => '5', + 120818 => '6', + 120819 => '7', + 120820 => '8', + 120821 => '9', + 120822 => '0', + 120823 => '1', + 120824 => '2', + 120825 => '3', + 120826 => '4', + 120827 => '5', + 120828 => '6', + 120829 => '7', + 120830 => '8', + 120831 => '9', + 125184 => '𞤢', + 125185 => '𞤣', + 125186 => '𞤤', + 125187 => '𞤥', + 125188 => '𞤦', + 125189 => '𞤧', + 125190 => '𞤨', + 125191 => '𞤩', + 125192 => '𞤪', + 125193 => '𞤫', + 125194 => '𞤬', + 125195 => '𞤭', + 125196 => '𞤮', + 125197 => '𞤯', + 125198 => '𞤰', + 125199 => '𞤱', + 125200 => '𞤲', + 125201 => '𞤳', + 125202 => '𞤴', + 125203 => '𞤵', + 125204 => '𞤶', + 125205 => '𞤷', + 125206 => '𞤸', + 125207 => '𞤹', + 125208 => '𞤺', + 125209 => '𞤻', + 125210 => '𞤼', + 125211 => '𞤽', + 125212 => '𞤾', + 125213 => '𞤿', + 125214 => '𞥀', + 125215 => '𞥁', + 125216 => '𞥂', + 125217 => '𞥃', + 126464 => 'ا', + 126465 => 'ب', + 126466 => 'ج', + 126467 => 'د', + 126469 => 'و', + 126470 => 'ز', + 126471 => 'ح', + 126472 => 'ط', + 126473 => 'ي', + 126474 => 'ك', + 126475 => 'ل', + 126476 => 'م', + 126477 => 'ن', + 126478 => 'س', + 126479 => 'ع', + 126480 => 'ف', + 126481 => 'ص', + 126482 => 'ق', + 126483 => 'ر', + 126484 => 'ش', + 126485 => 'ت', + 126486 => 'ث', + 126487 => 'خ', + 126488 => 'ذ', + 126489 => 'ض', + 126490 => 'ظ', + 126491 => 'غ', + 126492 => 'ٮ', + 126493 => 'ں', + 126494 => 'ڡ', + 126495 => 'ٯ', + 126497 => 'ب', + 126498 => 'ج', + 126500 => 'ه', + 126503 => 'ح', + 126505 => 'ي', + 126506 => 'ك', + 126507 => 'ل', + 126508 => 'م', + 126509 => 'ن', + 126510 => 'س', + 126511 => 'ع', + 126512 => 'ف', + 126513 => 'ص', + 126514 => 'ق', + 126516 => 'ش', + 126517 => 'ت', + 126518 => 'ث', + 126519 => 'خ', + 126521 => 'ض', + 126523 => 'غ', + 126530 => 'ج', + 126535 => 'ح', + 126537 => 'ي', + 126539 => 'ل', + 126541 => 'ن', + 126542 => 'س', + 126543 => 'ع', + 126545 => 'ص', + 126546 => 'ق', + 126548 => 'ش', + 126551 => 'خ', + 126553 => 'ض', + 126555 => 'غ', + 126557 => 'ں', + 126559 => 'ٯ', + 126561 => 'ب', + 126562 => 'ج', + 126564 => 'ه', + 126567 => 'ح', + 126568 => 'ط', + 126569 => 'ي', + 126570 => 'ك', + 126572 => 'م', + 126573 => 'ن', + 126574 => 'س', + 126575 => 'ع', + 126576 => 'ف', + 126577 => 'ص', + 126578 => 'ق', + 126580 => 'ش', + 126581 => 'ت', + 126582 => 'ث', + 126583 => 'خ', + 126585 => 'ض', + 126586 => 'ظ', + 126587 => 'غ', + 126588 => 'ٮ', + 126590 => 'ڡ', + 126592 => 'ا', + 126593 => 'ب', + 126594 => 'ج', + 126595 => 'د', + 126596 => 'ه', + 126597 => 'و', + 126598 => 'ز', + 126599 => 'ح', + 126600 => 'ط', + 126601 => 'ي', + 126603 => 'ل', + 126604 => 'م', + 126605 => 'ن', + 126606 => 'س', + 126607 => 'ع', + 126608 => 'ف', + 126609 => 'ص', + 126610 => 'ق', + 126611 => 'ر', + 126612 => 'ش', + 126613 => 'ت', + 126614 => 'ث', + 126615 => 'خ', + 126616 => 'ذ', + 126617 => 'ض', + 126618 => 'ظ', + 126619 => 'غ', + 126625 => 'ب', + 126626 => 'ج', + 126627 => 'د', + 126629 => 'و', + 126630 => 'ز', + 126631 => 'ح', + 126632 => 'ط', + 126633 => 'ي', + 126635 => 'ل', + 126636 => 'م', + 126637 => 'ن', + 126638 => 'س', + 126639 => 'ع', + 126640 => 'ف', + 126641 => 'ص', + 126642 => 'ق', + 126643 => 'ر', + 126644 => 'ش', + 126645 => 'ت', + 126646 => 'ث', + 126647 => 'خ', + 126648 => 'ذ', + 126649 => 'ض', + 126650 => 'ظ', + 126651 => 'غ', + 127274 => '〔s〕', + 127275 => 'c', + 127276 => 'r', + 127277 => 'cd', + 127278 => 'wz', + 127280 => 'a', + 127281 => 'b', + 127282 => 'c', + 127283 => 'd', + 127284 => 'e', + 127285 => 'f', + 127286 => 'g', + 127287 => 'h', + 127288 => 'i', + 127289 => 'j', + 127290 => 'k', + 127291 => 'l', + 127292 => 'm', + 127293 => 'n', + 127294 => 'o', + 127295 => 'p', + 127296 => 'q', + 127297 => 'r', + 127298 => 's', + 127299 => 't', + 127300 => 'u', + 127301 => 'v', + 127302 => 'w', + 127303 => 'x', + 127304 => 'y', + 127305 => 'z', + 127306 => 'hv', + 127307 => 'mv', + 127308 => 'sd', + 127309 => 'ss', + 127310 => 'ppv', + 127311 => 'wc', + 127338 => 'mc', + 127339 => 'md', + 127340 => 'mr', + 127376 => 'dj', + 127488 => 'ほか', + 127489 => 'ココ', + 127490 => 'サ', + 127504 => '手', + 127505 => '字', + 127506 => '双', + 127507 => 'デ', + 127508 => '二', + 127509 => '多', + 127510 => '解', + 127511 => '天', + 127512 => '交', + 127513 => '映', + 127514 => '無', + 127515 => '料', + 127516 => '前', + 127517 => '後', + 127518 => '再', + 127519 => '新', + 127520 => '初', + 127521 => '終', + 127522 => '生', + 127523 => '販', + 127524 => '声', + 127525 => '吹', + 127526 => '演', + 127527 => '投', + 127528 => '捕', + 127529 => '一', + 127530 => '三', + 127531 => '遊', + 127532 => '左', + 127533 => '中', + 127534 => '右', + 127535 => '指', + 127536 => '走', + 127537 => '打', + 127538 => '禁', + 127539 => '空', + 127540 => '合', + 127541 => '満', + 127542 => '有', + 127543 => '月', + 127544 => '申', + 127545 => '割', + 127546 => '営', + 127547 => '配', + 127552 => '〔本〕', + 127553 => '〔三〕', + 127554 => '〔二〕', + 127555 => '〔安〕', + 127556 => '〔点〕', + 127557 => '〔打〕', + 127558 => '〔盗〕', + 127559 => '〔勝〕', + 127560 => '〔敗〕', + 127568 => '得', + 127569 => '可', + 130032 => '0', + 130033 => '1', + 130034 => '2', + 130035 => '3', + 130036 => '4', + 130037 => '5', + 130038 => '6', + 130039 => '7', + 130040 => '8', + 130041 => '9', + 194560 => '丽', + 194561 => '丸', + 194562 => '乁', + 194563 => '𠄢', + 194564 => '你', + 194565 => '侮', + 194566 => '侻', + 194567 => '倂', + 194568 => '偺', + 194569 => '備', + 194570 => '僧', + 194571 => '像', + 194572 => '㒞', + 194573 => '𠘺', + 194574 => '免', + 194575 => '兔', + 194576 => '兤', + 194577 => '具', + 194578 => '𠔜', + 194579 => '㒹', + 194580 => '內', + 194581 => '再', + 194582 => '𠕋', + 194583 => '冗', + 194584 => '冤', + 194585 => '仌', + 194586 => '冬', + 194587 => '况', + 194588 => '𩇟', + 194589 => '凵', + 194590 => '刃', + 194591 => '㓟', + 194592 => '刻', + 194593 => '剆', + 194594 => '割', + 194595 => '剷', + 194596 => '㔕', + 194597 => '勇', + 194598 => '勉', + 194599 => '勤', + 194600 => '勺', + 194601 => '包', + 194602 => '匆', + 194603 => '北', + 194604 => '卉', + 194605 => '卑', + 194606 => '博', + 194607 => '即', + 194608 => '卽', + 194609 => '卿', + 194610 => '卿', + 194611 => '卿', + 194612 => '𠨬', + 194613 => '灰', + 194614 => '及', + 194615 => '叟', + 194616 => '𠭣', + 194617 => '叫', + 194618 => '叱', + 194619 => '吆', + 194620 => '咞', + 194621 => '吸', + 194622 => '呈', + 194623 => '周', + 194624 => '咢', + 194625 => '哶', + 194626 => '唐', + 194627 => '啓', + 194628 => '啣', + 194629 => '善', + 194630 => '善', + 194631 => '喙', + 194632 => '喫', + 194633 => '喳', + 194634 => '嗂', + 194635 => '圖', + 194636 => '嘆', + 194637 => '圗', + 194638 => '噑', + 194639 => '噴', + 194640 => '切', + 194641 => '壮', + 194642 => '城', + 194643 => '埴', + 194644 => '堍', + 194645 => '型', + 194646 => '堲', + 194647 => '報', + 194648 => '墬', + 194649 => '𡓤', + 194650 => '売', + 194651 => '壷', + 194652 => '夆', + 194653 => '多', + 194654 => '夢', + 194655 => '奢', + 194656 => '𡚨', + 194657 => '𡛪', + 194658 => '姬', + 194659 => '娛', + 194660 => '娧', + 194661 => '姘', + 194662 => '婦', + 194663 => '㛮', + 194665 => '嬈', + 194666 => '嬾', + 194667 => '嬾', + 194668 => '𡧈', + 194669 => '寃', + 194670 => '寘', + 194671 => '寧', + 194672 => '寳', + 194673 => '𡬘', + 194674 => '寿', + 194675 => '将', + 194677 => '尢', + 194678 => '㞁', + 194679 => '屠', + 194680 => '屮', + 194681 => '峀', + 194682 => '岍', + 194683 => '𡷤', + 194684 => '嵃', + 194685 => '𡷦', + 194686 => '嵮', + 194687 => '嵫', + 194688 => '嵼', + 194689 => '巡', + 194690 => '巢', + 194691 => '㠯', + 194692 => '巽', + 194693 => '帨', + 194694 => '帽', + 194695 => '幩', + 194696 => '㡢', + 194697 => '𢆃', + 194698 => '㡼', + 194699 => '庰', + 194700 => '庳', + 194701 => '庶', + 194702 => '廊', + 194703 => '𪎒', + 194704 => '廾', + 194705 => '𢌱', + 194706 => '𢌱', + 194707 => '舁', + 194708 => '弢', + 194709 => '弢', + 194710 => '㣇', + 194711 => '𣊸', + 194712 => '𦇚', + 194713 => '形', + 194714 => '彫', + 194715 => '㣣', + 194716 => '徚', + 194717 => '忍', + 194718 => '志', + 194719 => '忹', + 194720 => '悁', + 194721 => '㤺', + 194722 => '㤜', + 194723 => '悔', + 194724 => '𢛔', + 194725 => '惇', + 194726 => '慈', + 194727 => '慌', + 194728 => '慎', + 194729 => '慌', + 194730 => '慺', + 194731 => '憎', + 194732 => '憲', + 194733 => '憤', + 194734 => '憯', + 194735 => '懞', + 194736 => '懲', + 194737 => '懶', + 194738 => '成', + 194739 => '戛', + 194740 => '扝', + 194741 => '抱', + 194742 => '拔', + 194743 => '捐', + 194744 => '𢬌', + 194745 => '挽', + 194746 => '拼', + 194747 => '捨', + 194748 => '掃', + 194749 => '揤', + 194750 => '𢯱', + 194751 => '搢', + 194752 => '揅', + 194753 => '掩', + 194754 => '㨮', + 194755 => '摩', + 194756 => '摾', + 194757 => '撝', + 194758 => '摷', + 194759 => '㩬', + 194760 => '敏', + 194761 => '敬', + 194762 => '𣀊', + 194763 => '旣', + 194764 => '書', + 194765 => '晉', + 194766 => '㬙', + 194767 => '暑', + 194768 => '㬈', + 194769 => '㫤', + 194770 => '冒', + 194771 => '冕', + 194772 => '最', + 194773 => '暜', + 194774 => '肭', + 194775 => '䏙', + 194776 => '朗', + 194777 => '望', + 194778 => '朡', + 194779 => '杞', + 194780 => '杓', + 194781 => '𣏃', + 194782 => '㭉', + 194783 => '柺', + 194784 => '枅', + 194785 => '桒', + 194786 => '梅', + 194787 => '𣑭', + 194788 => '梎', + 194789 => '栟', + 194790 => '椔', + 194791 => '㮝', + 194792 => '楂', + 194793 => '榣', + 194794 => '槪', + 194795 => '檨', + 194796 => '𣚣', + 194797 => '櫛', + 194798 => '㰘', + 194799 => '次', + 194800 => '𣢧', + 194801 => '歔', + 194802 => '㱎', + 194803 => '歲', + 194804 => '殟', + 194805 => '殺', + 194806 => '殻', + 194807 => '𣪍', + 194808 => '𡴋', + 194809 => '𣫺', + 194810 => '汎', + 194811 => '𣲼', + 194812 => '沿', + 194813 => '泍', + 194814 => '汧', + 194815 => '洖', + 194816 => '派', + 194817 => '海', + 194818 => '流', + 194819 => '浩', + 194820 => '浸', + 194821 => '涅', + 194822 => '𣴞', + 194823 => '洴', + 194824 => '港', + 194825 => '湮', + 194826 => '㴳', + 194827 => '滋', + 194828 => '滇', + 194829 => '𣻑', + 194830 => '淹', + 194831 => '潮', + 194832 => '𣽞', + 194833 => '𣾎', + 194834 => '濆', + 194835 => '瀹', + 194836 => '瀞', + 194837 => '瀛', + 194838 => '㶖', + 194839 => '灊', + 194840 => '災', + 194841 => '灷', + 194842 => '炭', + 194843 => '𠔥', + 194844 => '煅', + 194845 => '𤉣', + 194846 => '熜', + 194848 => '爨', + 194849 => '爵', + 194850 => '牐', + 194851 => '𤘈', + 194852 => '犀', + 194853 => '犕', + 194854 => '𤜵', + 194855 => '𤠔', + 194856 => '獺', + 194857 => '王', + 194858 => '㺬', + 194859 => '玥', + 194860 => '㺸', + 194861 => '㺸', + 194862 => '瑇', + 194863 => '瑜', + 194864 => '瑱', + 194865 => '璅', + 194866 => '瓊', + 194867 => '㼛', + 194868 => '甤', + 194869 => '𤰶', + 194870 => '甾', + 194871 => '𤲒', + 194872 => '異', + 194873 => '𢆟', + 194874 => '瘐', + 194875 => '𤾡', + 194876 => '𤾸', + 194877 => '𥁄', + 194878 => '㿼', + 194879 => '䀈', + 194880 => '直', + 194881 => '𥃳', + 194882 => '𥃲', + 194883 => '𥄙', + 194884 => '𥄳', + 194885 => '眞', + 194886 => '真', + 194887 => '真', + 194888 => '睊', + 194889 => '䀹', + 194890 => '瞋', + 194891 => '䁆', + 194892 => '䂖', + 194893 => '𥐝', + 194894 => '硎', + 194895 => '碌', + 194896 => '磌', + 194897 => '䃣', + 194898 => '𥘦', + 194899 => '祖', + 194900 => '𥚚', + 194901 => '𥛅', + 194902 => '福', + 194903 => '秫', + 194904 => '䄯', + 194905 => '穀', + 194906 => '穊', + 194907 => '穏', + 194908 => '𥥼', + 194909 => '𥪧', + 194910 => '𥪧', + 194912 => '䈂', + 194913 => '𥮫', + 194914 => '篆', + 194915 => '築', + 194916 => '䈧', + 194917 => '𥲀', + 194918 => '糒', + 194919 => '䊠', + 194920 => '糨', + 194921 => '糣', + 194922 => '紀', + 194923 => '𥾆', + 194924 => '絣', + 194925 => '䌁', + 194926 => '緇', + 194927 => '縂', + 194928 => '繅', + 194929 => '䌴', + 194930 => '𦈨', + 194931 => '𦉇', + 194932 => '䍙', + 194933 => '𦋙', + 194934 => '罺', + 194935 => '𦌾', + 194936 => '羕', + 194937 => '翺', + 194938 => '者', + 194939 => '𦓚', + 194940 => '𦔣', + 194941 => '聠', + 194942 => '𦖨', + 194943 => '聰', + 194944 => '𣍟', + 194945 => '䏕', + 194946 => '育', + 194947 => '脃', + 194948 => '䐋', + 194949 => '脾', + 194950 => '媵', + 194951 => '𦞧', + 194952 => '𦞵', + 194953 => '𣎓', + 194954 => '𣎜', + 194955 => '舁', + 194956 => '舄', + 194957 => '辞', + 194958 => '䑫', + 194959 => '芑', + 194960 => '芋', + 194961 => '芝', + 194962 => '劳', + 194963 => '花', + 194964 => '芳', + 194965 => '芽', + 194966 => '苦', + 194967 => '𦬼', + 194968 => '若', + 194969 => '茝', + 194970 => '荣', + 194971 => '莭', + 194972 => '茣', + 194973 => '莽', + 194974 => '菧', + 194975 => '著', + 194976 => '荓', + 194977 => '菊', + 194978 => '菌', + 194979 => '菜', + 194980 => '𦰶', + 194981 => '𦵫', + 194982 => '𦳕', + 194983 => '䔫', + 194984 => '蓱', + 194985 => '蓳', + 194986 => '蔖', + 194987 => '𧏊', + 194988 => '蕤', + 194989 => '𦼬', + 194990 => '䕝', + 194991 => '䕡', + 194992 => '𦾱', + 194993 => '𧃒', + 194994 => '䕫', + 194995 => '虐', + 194996 => '虜', + 194997 => '虧', + 194998 => '虩', + 194999 => '蚩', + 195000 => '蚈', + 195001 => '蜎', + 195002 => '蛢', + 195003 => '蝹', + 195004 => '蜨', + 195005 => '蝫', + 195006 => '螆', + 195008 => '蟡', + 195009 => '蠁', + 195010 => '䗹', + 195011 => '衠', + 195012 => '衣', + 195013 => '𧙧', + 195014 => '裗', + 195015 => '裞', + 195016 => '䘵', + 195017 => '裺', + 195018 => '㒻', + 195019 => '𧢮', + 195020 => '𧥦', + 195021 => '䚾', + 195022 => '䛇', + 195023 => '誠', + 195024 => '諭', + 195025 => '變', + 195026 => '豕', + 195027 => '𧲨', + 195028 => '貫', + 195029 => '賁', + 195030 => '贛', + 195031 => '起', + 195032 => '𧼯', + 195033 => '𠠄', + 195034 => '跋', + 195035 => '趼', + 195036 => '跰', + 195037 => '𠣞', + 195038 => '軔', + 195039 => '輸', + 195040 => '𨗒', + 195041 => '𨗭', + 195042 => '邔', + 195043 => '郱', + 195044 => '鄑', + 195045 => '𨜮', + 195046 => '鄛', + 195047 => '鈸', + 195048 => '鋗', + 195049 => '鋘', + 195050 => '鉼', + 195051 => '鏹', + 195052 => '鐕', + 195053 => '𨯺', + 195054 => '開', + 195055 => '䦕', + 195056 => '閷', + 195057 => '𨵷', + 195058 => '䧦', + 195059 => '雃', + 195060 => '嶲', + 195061 => '霣', + 195062 => '𩅅', + 195063 => '𩈚', + 195064 => '䩮', + 195065 => '䩶', + 195066 => '韠', + 195067 => '𩐊', + 195068 => '䪲', + 195069 => '𩒖', + 195070 => '頋', + 195071 => '頋', + 195072 => '頩', + 195073 => '𩖶', + 195074 => '飢', + 195075 => '䬳', + 195076 => '餩', + 195077 => '馧', + 195078 => '駂', + 195079 => '駾', + 195080 => '䯎', + 195081 => '𩬰', + 195082 => '鬒', + 195083 => '鱀', + 195084 => '鳽', + 195085 => '䳎', + 195086 => '䳭', + 195087 => '鵧', + 195088 => '𪃎', + 195089 => '䳸', + 195090 => '𪄅', + 195091 => '𪈎', + 195092 => '𪊑', + 195093 => '麻', + 195094 => '䵖', + 195095 => '黹', + 195096 => '黾', + 195097 => '鼅', + 195098 => '鼏', + 195099 => '鼖', + 195100 => '鼻', + 195101 => '𪘀', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php new file mode 100644 index 0000000..1958e37 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php @@ -0,0 +1,65 @@ + 9, + 2509 => 9, + 2637 => 9, + 2765 => 9, + 2893 => 9, + 3021 => 9, + 3149 => 9, + 3277 => 9, + 3387 => 9, + 3388 => 9, + 3405 => 9, + 3530 => 9, + 3642 => 9, + 3770 => 9, + 3972 => 9, + 4153 => 9, + 4154 => 9, + 5908 => 9, + 5940 => 9, + 6098 => 9, + 6752 => 9, + 6980 => 9, + 7082 => 9, + 7083 => 9, + 7154 => 9, + 7155 => 9, + 11647 => 9, + 43014 => 9, + 43052 => 9, + 43204 => 9, + 43347 => 9, + 43456 => 9, + 43766 => 9, + 44013 => 9, + 68159 => 9, + 69702 => 9, + 69759 => 9, + 69817 => 9, + 69939 => 9, + 69940 => 9, + 70080 => 9, + 70197 => 9, + 70378 => 9, + 70477 => 9, + 70722 => 9, + 70850 => 9, + 71103 => 9, + 71231 => 9, + 71350 => 9, + 71467 => 9, + 71737 => 9, + 71997 => 9, + 71998 => 9, + 72160 => 9, + 72244 => 9, + 72263 => 9, + 72345 => 9, + 72767 => 9, + 73028 => 9, + 73029 => 9, + 73111 => 9, +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap.php b/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap.php new file mode 100644 index 0000000..57c7835 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (\PHP_VERSION_ID < 70400) { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} else { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap80.php b/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap80.php new file mode 100644 index 0000000..6c2b729 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/bootstrap80.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (!function_exists('idn_to_ascii')) { + function idn_to_ascii(string $domain, int $flags = 0, int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } +} +if (!function_exists('idn_to_utf8')) { + function idn_to_utf8(string $domain, int $flags = 0, int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-idn/composer.json b/plugins/email/vendor/symfony/polyfill-intl-idn/composer.json new file mode 100644 index 0000000..450d1e7 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-idn/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/polyfill-intl-idn", + "type": "library", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/LICENSE b/plugins/email/vendor/symfony/polyfill-intl-normalizer/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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/plugins/email/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 0000000..4443c23 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Normalizer; + +/** + * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. + * + * It has been validated with Unicode 6.3 Normalization Conformance Test. + * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. + * + * @author Nicolas Grekas + * + * @internal + */ +class Normalizer +{ + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized(string $s, int $form = self::FORM_C) + { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { + return false; + } + if (!isset($s[strspn($s, self::$ASCII)])) { + return true; + } + if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return self::normalize($s, $form) === $s; + } + + public static function normalize(string $s, int $form = self::FORM_C) + { + if (!preg_match('//u', $s)) { + return false; + } + + switch ($form) { + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: + if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { + return $s; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = \strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = $combClass[$uchr] ?? 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = \ord($lastUchr[2]) - 0x80; + $V = \ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = \ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = []; + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { + $uchr = $j; + + $j = \strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".\chr(0xA7 + $j)) + : ("\xE1\x87".\chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/README.md b/plugins/email/vendor/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 0000000..15060c5 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Intl: Normalizer +=================================== + +This component provides a fallback implementation for the +[`Normalizer`](https://php.net/Normalizer) class provided +by the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 0000000..0fdfc89 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '΅' => '΅', + 'Ά' => 'Ά', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ὲ' => 'ὲ', + 'ὴ' => 'ὴ', + 'ὶ' => 'ὶ', + 'ὸ' => 'ὸ', + 'ὺ' => 'ὺ', + 'ὼ' => 'ὼ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'ᾼ', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 0000000..5a3e8e0 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '̀' => '̀', + '́' => '́', + '̓' => '̓', + '̈́' => '̈́', + 'ʹ' => 'ʹ', + ';' => ';', + '΅' => '΅', + 'Ά' => 'Ά', + '·' => '·', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'ढ़' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ড়' => 'ড়', + 'ঢ়' => 'ঢ়', + 'য়' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'ਖ਼' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'ਜ਼' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ଡ଼' => 'ଡ଼', + 'ଢ଼' => 'ଢ଼', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'གྷ' => 'གྷ', + 'ཌྷ' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'ཱྀ' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'ྜྷ' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'ྐྵ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ά' => 'ά', + 'ὲ' => 'ὲ', + 'έ' => 'έ', + 'ὴ' => 'ὴ', + 'ή' => 'ή', + 'ὶ' => 'ὶ', + 'ί' => 'ί', + 'ὸ' => 'ὸ', + 'ό' => 'ό', + 'ὺ' => 'ὺ', + 'ύ' => 'ύ', + 'ὼ' => 'ὼ', + 'ώ' => 'ώ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'Ά' => 'Ά', + 'ᾼ' => 'ᾼ', + 'ι' => 'ι', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'Έ', + 'Ὴ' => 'Ὴ', + 'Ή' => 'Ή', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ΐ' => 'ΐ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'Ί' => 'Ί', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ΰ' => 'ΰ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ύ' => 'Ύ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + '΅' => '΅', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'Ό', + 'Ὼ' => 'Ὼ', + 'Ώ' => 'Ώ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + ' ' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'Å' => 'Å', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => '⫝̸', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '塚' => '塚', + '晴' => '晴', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'יִ' => 'יִ', + 'ײַ' => 'ײַ', + 'שׁ' => 'שׁ', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּׁ', + 'שּׂ' => 'שּׂ', + 'אַ' => 'אַ', + 'אָ' => 'אָ', + 'אּ' => 'אּ', + 'בּ' => 'בּ', + 'גּ' => 'גּ', + 'דּ' => 'דּ', + 'הּ' => 'הּ', + 'וּ' => 'וּ', + 'זּ' => 'זּ', + 'טּ' => 'טּ', + 'יּ' => 'יּ', + 'ךּ' => 'ךּ', + 'כּ' => 'כּ', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'נּ' => 'נּ', + 'סּ' => 'סּ', + 'ףּ' => 'ףּ', + 'פּ' => 'פּ', + 'צּ' => 'צּ', + 'קּ' => 'קּ', + 'רּ' => 'רּ', + 'שּ' => 'שּ', + 'תּ' => 'תּ', + 'וֹ' => 'וֹ', + 'בֿ' => 'בֿ', + 'כֿ' => 'כֿ', + 'פֿ' => 'פֿ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', + '𝅗𝅥' => '𝅗𝅥', + '𝅘𝅥' => '𝅘𝅥', + '𝅘𝅥𝅮' => '𝅘𝅥𝅮', + '𝅘𝅥𝅯' => '𝅘𝅥𝅯', + '𝅘𝅥𝅰' => '𝅘𝅥𝅰', + '𝅘𝅥𝅱' => '𝅘𝅥𝅱', + '𝅘𝅥𝅲' => '𝅘𝅥𝅲', + '𝆹𝅥' => '𝆹𝅥', + '𝆺𝅥' => '𝆺𝅥', + '𝆹𝅥𝅮' => '𝆹𝅥𝅮', + '𝆺𝅥𝅮' => '𝆺𝅥𝅮', + '𝆹𝅥𝅯' => '𝆹𝅥𝅯', + '𝆺𝅥𝅯' => '𝆺𝅥𝅯', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 0000000..ec90f36 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php @@ -0,0 +1,876 @@ + 230, + '́' => 230, + '̂' => 230, + '̃' => 230, + '̄' => 230, + '̅' => 230, + '̆' => 230, + '̇' => 230, + '̈' => 230, + '̉' => 230, + '̊' => 230, + '̋' => 230, + '̌' => 230, + '̍' => 230, + '̎' => 230, + '̏' => 230, + '̐' => 230, + '̑' => 230, + '̒' => 230, + '̓' => 230, + '̔' => 230, + '̕' => 232, + '̖' => 220, + '̗' => 220, + '̘' => 220, + '̙' => 220, + '̚' => 232, + '̛' => 216, + '̜' => 220, + '̝' => 220, + '̞' => 220, + '̟' => 220, + '̠' => 220, + '̡' => 202, + '̢' => 202, + '̣' => 220, + '̤' => 220, + '̥' => 220, + '̦' => 220, + '̧' => 202, + '̨' => 202, + '̩' => 220, + '̪' => 220, + '̫' => 220, + '̬' => 220, + '̭' => 220, + '̮' => 220, + '̯' => 220, + '̰' => 220, + '̱' => 220, + '̲' => 220, + '̳' => 220, + '̴' => 1, + '̵' => 1, + '̶' => 1, + '̷' => 1, + '̸' => 1, + '̹' => 220, + '̺' => 220, + '̻' => 220, + '̼' => 220, + '̽' => 230, + '̾' => 230, + '̿' => 230, + '̀' => 230, + '́' => 230, + '͂' => 230, + '̓' => 230, + '̈́' => 230, + 'ͅ' => 240, + '͆' => 230, + '͇' => 220, + '͈' => 220, + '͉' => 220, + '͊' => 230, + '͋' => 230, + '͌' => 230, + '͍' => 220, + '͎' => 220, + '͐' => 230, + '͑' => 230, + '͒' => 230, + '͓' => 220, + '͔' => 220, + '͕' => 220, + '͖' => 220, + '͗' => 230, + '͘' => 232, + '͙' => 220, + '͚' => 220, + '͛' => 230, + '͜' => 233, + '͝' => 234, + '͞' => 234, + '͟' => 233, + '͠' => 234, + '͡' => 234, + '͢' => 233, + 'ͣ' => 230, + 'ͤ' => 230, + 'ͥ' => 230, + 'ͦ' => 230, + 'ͧ' => 230, + 'ͨ' => 230, + 'ͩ' => 230, + 'ͪ' => 230, + 'ͫ' => 230, + 'ͬ' => 230, + 'ͭ' => 230, + 'ͮ' => 230, + 'ͯ' => 230, + '҃' => 230, + '҄' => 230, + '҅' => 230, + '҆' => 230, + '҇' => 230, + '֑' => 220, + '֒' => 230, + '֓' => 230, + '֔' => 230, + '֕' => 230, + '֖' => 220, + '֗' => 230, + '֘' => 230, + '֙' => 230, + '֚' => 222, + '֛' => 220, + '֜' => 230, + '֝' => 230, + '֞' => 230, + '֟' => 230, + '֠' => 230, + '֡' => 230, + '֢' => 220, + '֣' => 220, + '֤' => 220, + '֥' => 220, + '֦' => 220, + '֧' => 220, + '֨' => 230, + '֩' => 230, + '֪' => 220, + '֫' => 230, + '֬' => 230, + '֭' => 222, + '֮' => 228, + '֯' => 230, + 'ְ' => 10, + 'ֱ' => 11, + 'ֲ' => 12, + 'ֳ' => 13, + 'ִ' => 14, + 'ֵ' => 15, + 'ֶ' => 16, + 'ַ' => 17, + 'ָ' => 18, + 'ֹ' => 19, + 'ֺ' => 19, + 'ֻ' => 20, + 'ּ' => 21, + 'ֽ' => 22, + 'ֿ' => 23, + 'ׁ' => 24, + 'ׂ' => 25, + 'ׄ' => 230, + 'ׅ' => 220, + 'ׇ' => 18, + 'ؐ' => 230, + 'ؑ' => 230, + 'ؒ' => 230, + 'ؓ' => 230, + 'ؔ' => 230, + 'ؕ' => 230, + 'ؖ' => 230, + 'ؗ' => 230, + 'ؘ' => 30, + 'ؙ' => 31, + 'ؚ' => 32, + 'ً' => 27, + 'ٌ' => 28, + 'ٍ' => 29, + 'َ' => 30, + 'ُ' => 31, + 'ِ' => 32, + 'ّ' => 33, + 'ْ' => 34, + 'ٓ' => 230, + 'ٔ' => 230, + 'ٕ' => 220, + 'ٖ' => 220, + 'ٗ' => 230, + '٘' => 230, + 'ٙ' => 230, + 'ٚ' => 230, + 'ٛ' => 230, + 'ٜ' => 220, + 'ٝ' => 230, + 'ٞ' => 230, + 'ٟ' => 220, + 'ٰ' => 35, + 'ۖ' => 230, + 'ۗ' => 230, + 'ۘ' => 230, + 'ۙ' => 230, + 'ۚ' => 230, + 'ۛ' => 230, + 'ۜ' => 230, + '۟' => 230, + '۠' => 230, + 'ۡ' => 230, + 'ۢ' => 230, + 'ۣ' => 220, + 'ۤ' => 230, + 'ۧ' => 230, + 'ۨ' => 230, + '۪' => 220, + '۫' => 230, + '۬' => 230, + 'ۭ' => 220, + 'ܑ' => 36, + 'ܰ' => 230, + 'ܱ' => 220, + 'ܲ' => 230, + 'ܳ' => 230, + 'ܴ' => 220, + 'ܵ' => 230, + 'ܶ' => 230, + 'ܷ' => 220, + 'ܸ' => 220, + 'ܹ' => 220, + 'ܺ' => 230, + 'ܻ' => 220, + 'ܼ' => 220, + 'ܽ' => 230, + 'ܾ' => 220, + 'ܿ' => 230, + '݀' => 230, + '݁' => 230, + '݂' => 220, + '݃' => 230, + '݄' => 220, + '݅' => 230, + '݆' => 220, + '݇' => 230, + '݈' => 220, + '݉' => 230, + '݊' => 230, + '߫' => 230, + '߬' => 230, + '߭' => 230, + '߮' => 230, + '߯' => 230, + '߰' => 230, + '߱' => 230, + '߲' => 220, + '߳' => 230, + '߽' => 220, + 'ࠖ' => 230, + 'ࠗ' => 230, + '࠘' => 230, + '࠙' => 230, + 'ࠛ' => 230, + 'ࠜ' => 230, + 'ࠝ' => 230, + 'ࠞ' => 230, + 'ࠟ' => 230, + 'ࠠ' => 230, + 'ࠡ' => 230, + 'ࠢ' => 230, + 'ࠣ' => 230, + 'ࠥ' => 230, + 'ࠦ' => 230, + 'ࠧ' => 230, + 'ࠩ' => 230, + 'ࠪ' => 230, + 'ࠫ' => 230, + 'ࠬ' => 230, + '࠭' => 230, + '࡙' => 220, + '࡚' => 220, + '࡛' => 220, + '࣓' => 220, + 'ࣔ' => 230, + 'ࣕ' => 230, + 'ࣖ' => 230, + 'ࣗ' => 230, + 'ࣘ' => 230, + 'ࣙ' => 230, + 'ࣚ' => 230, + 'ࣛ' => 230, + 'ࣜ' => 230, + 'ࣝ' => 230, + 'ࣞ' => 230, + 'ࣟ' => 230, + '࣠' => 230, + '࣡' => 230, + 'ࣣ' => 220, + 'ࣤ' => 230, + 'ࣥ' => 230, + 'ࣦ' => 220, + 'ࣧ' => 230, + 'ࣨ' => 230, + 'ࣩ' => 220, + '࣪' => 230, + '࣫' => 230, + '࣬' => 230, + '࣭' => 220, + '࣮' => 220, + '࣯' => 220, + 'ࣰ' => 27, + 'ࣱ' => 28, + 'ࣲ' => 29, + 'ࣳ' => 230, + 'ࣴ' => 230, + 'ࣵ' => 230, + 'ࣶ' => 220, + 'ࣷ' => 230, + 'ࣸ' => 230, + 'ࣹ' => 220, + 'ࣺ' => 220, + 'ࣻ' => 230, + 'ࣼ' => 230, + 'ࣽ' => 230, + 'ࣾ' => 230, + 'ࣿ' => 230, + '़' => 7, + '्' => 9, + '॑' => 230, + '॒' => 220, + '॓' => 230, + '॔' => 230, + '়' => 7, + '্' => 9, + '৾' => 230, + '਼' => 7, + '੍' => 9, + '઼' => 7, + '્' => 9, + '଼' => 7, + '୍' => 9, + '்' => 9, + '్' => 9, + 'ౕ' => 84, + 'ౖ' => 91, + '಼' => 7, + '್' => 9, + '഻' => 9, + '഼' => 9, + '്' => 9, + '්' => 9, + 'ุ' => 103, + 'ู' => 103, + 'ฺ' => 9, + '่' => 107, + '้' => 107, + '๊' => 107, + '๋' => 107, + 'ຸ' => 118, + 'ູ' => 118, + '຺' => 9, + '່' => 122, + '້' => 122, + '໊' => 122, + '໋' => 122, + '༘' => 220, + '༙' => 220, + '༵' => 220, + '༷' => 220, + '༹' => 216, + 'ཱ' => 129, + 'ི' => 130, + 'ུ' => 132, + 'ེ' => 130, + 'ཻ' => 130, + 'ོ' => 130, + 'ཽ' => 130, + 'ྀ' => 130, + 'ྂ' => 230, + 'ྃ' => 230, + '྄' => 9, + '྆' => 230, + '྇' => 230, + '࿆' => 220, + '့' => 7, + '္' => 9, + '်' => 9, + 'ႍ' => 220, + '፝' => 230, + '፞' => 230, + '፟' => 230, + '᜔' => 9, + '᜴' => 9, + '្' => 9, + '៝' => 230, + 'ᢩ' => 228, + '᤹' => 222, + '᤺' => 230, + '᤻' => 220, + 'ᨗ' => 230, + 'ᨘ' => 220, + '᩠' => 9, + '᩵' => 230, + '᩶' => 230, + '᩷' => 230, + '᩸' => 230, + '᩹' => 230, + '᩺' => 230, + '᩻' => 230, + '᩼' => 230, + '᩿' => 220, + '᪰' => 230, + '᪱' => 230, + '᪲' => 230, + '᪳' => 230, + '᪴' => 230, + '᪵' => 220, + '᪶' => 220, + '᪷' => 220, + '᪸' => 220, + '᪹' => 220, + '᪺' => 220, + '᪻' => 230, + '᪼' => 230, + '᪽' => 220, + 'ᪿ' => 220, + 'ᫀ' => 220, + '᬴' => 7, + '᭄' => 9, + '᭫' => 230, + '᭬' => 220, + '᭭' => 230, + '᭮' => 230, + '᭯' => 230, + '᭰' => 230, + '᭱' => 230, + '᭲' => 230, + '᭳' => 230, + '᮪' => 9, + '᮫' => 9, + '᯦' => 7, + '᯲' => 9, + '᯳' => 9, + '᰷' => 7, + '᳐' => 230, + '᳑' => 230, + '᳒' => 230, + '᳔' => 1, + '᳕' => 220, + '᳖' => 220, + '᳗' => 220, + '᳘' => 220, + '᳙' => 220, + '᳚' => 230, + '᳛' => 230, + '᳜' => 220, + '᳝' => 220, + '᳞' => 220, + '᳟' => 220, + '᳠' => 230, + '᳢' => 1, + '᳣' => 1, + '᳤' => 1, + '᳥' => 1, + '᳦' => 1, + '᳧' => 1, + '᳨' => 1, + '᳭' => 220, + '᳴' => 230, + '᳸' => 230, + '᳹' => 230, + '᷀' => 230, + '᷁' => 230, + '᷂' => 220, + '᷃' => 230, + '᷄' => 230, + '᷅' => 230, + '᷆' => 230, + '᷇' => 230, + '᷈' => 230, + '᷉' => 230, + '᷊' => 220, + '᷋' => 230, + '᷌' => 230, + '᷍' => 234, + '᷎' => 214, + '᷏' => 220, + '᷐' => 202, + '᷑' => 230, + '᷒' => 230, + 'ᷓ' => 230, + 'ᷔ' => 230, + 'ᷕ' => 230, + 'ᷖ' => 230, + 'ᷗ' => 230, + 'ᷘ' => 230, + 'ᷙ' => 230, + 'ᷚ' => 230, + 'ᷛ' => 230, + 'ᷜ' => 230, + 'ᷝ' => 230, + 'ᷞ' => 230, + 'ᷟ' => 230, + 'ᷠ' => 230, + 'ᷡ' => 230, + 'ᷢ' => 230, + 'ᷣ' => 230, + 'ᷤ' => 230, + 'ᷥ' => 230, + 'ᷦ' => 230, + 'ᷧ' => 230, + 'ᷨ' => 230, + 'ᷩ' => 230, + 'ᷪ' => 230, + 'ᷫ' => 230, + 'ᷬ' => 230, + 'ᷭ' => 230, + 'ᷮ' => 230, + 'ᷯ' => 230, + 'ᷰ' => 230, + 'ᷱ' => 230, + 'ᷲ' => 230, + 'ᷳ' => 230, + 'ᷴ' => 230, + '᷵' => 230, + '᷶' => 232, + '᷷' => 228, + '᷸' => 228, + '᷹' => 220, + '᷻' => 230, + '᷼' => 233, + '᷽' => 220, + '᷾' => 230, + '᷿' => 220, + '⃐' => 230, + '⃑' => 230, + '⃒' => 1, + '⃓' => 1, + '⃔' => 230, + '⃕' => 230, + '⃖' => 230, + '⃗' => 230, + '⃘' => 1, + '⃙' => 1, + '⃚' => 1, + '⃛' => 230, + '⃜' => 230, + '⃡' => 230, + '⃥' => 1, + '⃦' => 1, + '⃧' => 230, + '⃨' => 220, + '⃩' => 230, + '⃪' => 1, + '⃫' => 1, + '⃬' => 220, + '⃭' => 220, + '⃮' => 220, + '⃯' => 220, + '⃰' => 230, + '⳯' => 230, + '⳰' => 230, + '⳱' => 230, + '⵿' => 9, + 'ⷠ' => 230, + 'ⷡ' => 230, + 'ⷢ' => 230, + 'ⷣ' => 230, + 'ⷤ' => 230, + 'ⷥ' => 230, + 'ⷦ' => 230, + 'ⷧ' => 230, + 'ⷨ' => 230, + 'ⷩ' => 230, + 'ⷪ' => 230, + 'ⷫ' => 230, + 'ⷬ' => 230, + 'ⷭ' => 230, + 'ⷮ' => 230, + 'ⷯ' => 230, + 'ⷰ' => 230, + 'ⷱ' => 230, + 'ⷲ' => 230, + 'ⷳ' => 230, + 'ⷴ' => 230, + 'ⷵ' => 230, + 'ⷶ' => 230, + 'ⷷ' => 230, + 'ⷸ' => 230, + 'ⷹ' => 230, + 'ⷺ' => 230, + 'ⷻ' => 230, + 'ⷼ' => 230, + 'ⷽ' => 230, + 'ⷾ' => 230, + 'ⷿ' => 230, + '〪' => 218, + '〫' => 228, + '〬' => 232, + '〭' => 222, + '〮' => 224, + '〯' => 224, + '゙' => 8, + '゚' => 8, + '꙯' => 230, + 'ꙴ' => 230, + 'ꙵ' => 230, + 'ꙶ' => 230, + 'ꙷ' => 230, + 'ꙸ' => 230, + 'ꙹ' => 230, + 'ꙺ' => 230, + 'ꙻ' => 230, + '꙼' => 230, + '꙽' => 230, + 'ꚞ' => 230, + 'ꚟ' => 230, + '꛰' => 230, + '꛱' => 230, + '꠆' => 9, + '꠬' => 9, + '꣄' => 9, + '꣠' => 230, + '꣡' => 230, + '꣢' => 230, + '꣣' => 230, + '꣤' => 230, + '꣥' => 230, + '꣦' => 230, + '꣧' => 230, + '꣨' => 230, + '꣩' => 230, + '꣪' => 230, + '꣫' => 230, + '꣬' => 230, + '꣭' => 230, + '꣮' => 230, + '꣯' => 230, + '꣰' => 230, + '꣱' => 230, + '꤫' => 220, + '꤬' => 220, + '꤭' => 220, + '꥓' => 9, + '꦳' => 7, + '꧀' => 9, + 'ꪰ' => 230, + 'ꪲ' => 230, + 'ꪳ' => 230, + 'ꪴ' => 220, + 'ꪷ' => 230, + 'ꪸ' => 230, + 'ꪾ' => 230, + '꪿' => 230, + '꫁' => 230, + '꫶' => 9, + '꯭' => 9, + 'ﬞ' => 26, + '︠' => 230, + '︡' => 230, + '︢' => 230, + '︣' => 230, + '︤' => 230, + '︥' => 230, + '︦' => 230, + '︧' => 220, + '︨' => 220, + '︩' => 220, + '︪' => 220, + '︫' => 220, + '︬' => 220, + '︭' => 220, + '︮' => 230, + '︯' => 230, + '𐇽' => 220, + '𐋠' => 220, + '𐍶' => 230, + '𐍷' => 230, + '𐍸' => 230, + '𐍹' => 230, + '𐍺' => 230, + '𐨍' => 220, + '𐨏' => 230, + '𐨸' => 230, + '𐨹' => 1, + '𐨺' => 220, + '𐨿' => 9, + '𐫥' => 230, + '𐫦' => 220, + '𐴤' => 230, + '𐴥' => 230, + '𐴦' => 230, + '𐴧' => 230, + '𐺫' => 230, + '𐺬' => 230, + '𐽆' => 220, + '𐽇' => 220, + '𐽈' => 230, + '𐽉' => 230, + '𐽊' => 230, + '𐽋' => 220, + '𐽌' => 230, + '𐽍' => 220, + '𐽎' => 220, + '𐽏' => 220, + '𐽐' => 220, + '𑁆' => 9, + '𑁿' => 9, + '𑂹' => 9, + '𑂺' => 7, + '𑄀' => 230, + '𑄁' => 230, + '𑄂' => 230, + '𑄳' => 9, + '𑄴' => 9, + '𑅳' => 7, + '𑇀' => 9, + '𑇊' => 7, + '𑈵' => 9, + '𑈶' => 7, + '𑋩' => 7, + '𑋪' => 9, + '𑌻' => 7, + '𑌼' => 7, + '𑍍' => 9, + '𑍦' => 230, + '𑍧' => 230, + '𑍨' => 230, + '𑍩' => 230, + '𑍪' => 230, + '𑍫' => 230, + '𑍬' => 230, + '𑍰' => 230, + '𑍱' => 230, + '𑍲' => 230, + '𑍳' => 230, + '𑍴' => 230, + '𑑂' => 9, + '𑑆' => 7, + '𑑞' => 230, + '𑓂' => 9, + '𑓃' => 7, + '𑖿' => 9, + '𑗀' => 7, + '𑘿' => 9, + '𑚶' => 9, + '𑚷' => 7, + '𑜫' => 9, + '𑠹' => 9, + '𑠺' => 7, + '𑤽' => 9, + '𑤾' => 9, + '𑥃' => 7, + '𑧠' => 9, + '𑨴' => 9, + '𑩇' => 9, + '𑪙' => 9, + '𑰿' => 9, + '𑵂' => 7, + '𑵄' => 9, + '𑵅' => 9, + '𑶗' => 9, + '𖫰' => 1, + '𖫱' => 1, + '𖫲' => 1, + '𖫳' => 1, + '𖫴' => 1, + '𖬰' => 230, + '𖬱' => 230, + '𖬲' => 230, + '𖬳' => 230, + '𖬴' => 230, + '𖬵' => 230, + '𖬶' => 230, + '𖿰' => 6, + '𖿱' => 6, + '𛲞' => 1, + '𝅥' => 216, + '𝅦' => 216, + '𝅧' => 1, + '𝅨' => 1, + '𝅩' => 1, + '𝅭' => 226, + '𝅮' => 216, + '𝅯' => 216, + '𝅰' => 216, + '𝅱' => 216, + '𝅲' => 216, + '𝅻' => 220, + '𝅼' => 220, + '𝅽' => 220, + '𝅾' => 220, + '𝅿' => 220, + '𝆀' => 220, + '𝆁' => 220, + '𝆂' => 220, + '𝆅' => 230, + '𝆆' => 230, + '𝆇' => 230, + '𝆈' => 230, + '𝆉' => 230, + '𝆊' => 220, + '𝆋' => 220, + '𝆪' => 230, + '𝆫' => 230, + '𝆬' => 230, + '𝆭' => 230, + '𝉂' => 230, + '𝉃' => 230, + '𝉄' => 230, + '𞀀' => 230, + '𞀁' => 230, + '𞀂' => 230, + '𞀃' => 230, + '𞀄' => 230, + '𞀅' => 230, + '𞀆' => 230, + '𞀈' => 230, + '𞀉' => 230, + '𞀊' => 230, + '𞀋' => 230, + '𞀌' => 230, + '𞀍' => 230, + '𞀎' => 230, + '𞀏' => 230, + '𞀐' => 230, + '𞀑' => 230, + '𞀒' => 230, + '𞀓' => 230, + '𞀔' => 230, + '𞀕' => 230, + '𞀖' => 230, + '𞀗' => 230, + '𞀘' => 230, + '𞀛' => 230, + '𞀜' => 230, + '𞀝' => 230, + '𞀞' => 230, + '𞀟' => 230, + '𞀠' => 230, + '𞀡' => 230, + '𞀣' => 230, + '𞀤' => 230, + '𞀦' => 230, + '𞀧' => 230, + '𞀨' => 230, + '𞀩' => 230, + '𞀪' => 230, + '𞄰' => 230, + '𞄱' => 230, + '𞄲' => 230, + '𞄳' => 230, + '𞄴' => 230, + '𞄵' => 230, + '𞄶' => 230, + '𞋬' => 230, + '𞋭' => 230, + '𞋮' => 230, + '𞋯' => 230, + '𞣐' => 220, + '𞣑' => 220, + '𞣒' => 220, + '𞣓' => 220, + '𞣔' => 220, + '𞣕' => 220, + '𞣖' => 220, + '𞥄' => 230, + '𞥅' => 230, + '𞥆' => 230, + '𞥇' => 230, + '𞥈' => 230, + '𞥉' => 230, + '𞥊' => 7, +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 0000000..1574902 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php @@ -0,0 +1,3695 @@ + ' ', + '¨' => ' ̈', + 'ª' => 'a', + '¯' => ' ̄', + '²' => '2', + '³' => '3', + '´' => ' ́', + 'µ' => 'μ', + '¸' => ' ̧', + '¹' => '1', + 'º' => 'o', + '¼' => '1⁄4', + '½' => '1⁄2', + '¾' => '3⁄4', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ŀ' => 'L·', + 'ŀ' => 'l·', + 'ʼn' => 'ʼn', + 'ſ' => 's', + 'DŽ' => 'DŽ', + 'Dž' => 'Dž', + 'dž' => 'dž', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ʰ' => 'h', + 'ʱ' => 'ɦ', + 'ʲ' => 'j', + 'ʳ' => 'r', + 'ʴ' => 'ɹ', + 'ʵ' => 'ɻ', + 'ʶ' => 'ʁ', + 'ʷ' => 'w', + 'ʸ' => 'y', + '˘' => ' ̆', + '˙' => ' ̇', + '˚' => ' ̊', + '˛' => ' ̨', + '˜' => ' ̃', + '˝' => ' ̋', + 'ˠ' => 'ɣ', + 'ˡ' => 'l', + 'ˢ' => 's', + 'ˣ' => 'x', + 'ˤ' => 'ʕ', + 'ͺ' => ' ͅ', + '΄' => ' ́', + '΅' => ' ̈́', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϒ' => 'Υ', + 'ϓ' => 'Ύ', + 'ϔ' => 'Ϋ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϲ' => 'ς', + 'ϴ' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'և' => 'եւ', + 'ٵ' => 'اٴ', + 'ٶ' => 'وٴ', + 'ٷ' => 'ۇٴ', + 'ٸ' => 'يٴ', + 'ำ' => 'ํา', + 'ຳ' => 'ໍາ', + 'ໜ' => 'ຫນ', + 'ໝ' => 'ຫມ', + '༌' => '་', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ჼ' => 'ნ', + 'ᴬ' => 'A', + 'ᴭ' => 'Æ', + 'ᴮ' => 'B', + 'ᴰ' => 'D', + 'ᴱ' => 'E', + 'ᴲ' => 'Ǝ', + 'ᴳ' => 'G', + 'ᴴ' => 'H', + 'ᴵ' => 'I', + 'ᴶ' => 'J', + 'ᴷ' => 'K', + 'ᴸ' => 'L', + 'ᴹ' => 'M', + 'ᴺ' => 'N', + 'ᴼ' => 'O', + 'ᴽ' => 'Ȣ', + 'ᴾ' => 'P', + 'ᴿ' => 'R', + 'ᵀ' => 'T', + 'ᵁ' => 'U', + 'ᵂ' => 'W', + 'ᵃ' => 'a', + 'ᵄ' => 'ɐ', + 'ᵅ' => 'ɑ', + 'ᵆ' => 'ᴂ', + 'ᵇ' => 'b', + 'ᵈ' => 'd', + 'ᵉ' => 'e', + 'ᵊ' => 'ə', + 'ᵋ' => 'ɛ', + 'ᵌ' => 'ɜ', + 'ᵍ' => 'g', + 'ᵏ' => 'k', + 'ᵐ' => 'm', + 'ᵑ' => 'ŋ', + 'ᵒ' => 'o', + 'ᵓ' => 'ɔ', + 'ᵔ' => 'ᴖ', + 'ᵕ' => 'ᴗ', + 'ᵖ' => 'p', + 'ᵗ' => 't', + 'ᵘ' => 'u', + 'ᵙ' => 'ᴝ', + 'ᵚ' => 'ɯ', + 'ᵛ' => 'v', + 'ᵜ' => 'ᴥ', + 'ᵝ' => 'β', + 'ᵞ' => 'γ', + 'ᵟ' => 'δ', + 'ᵠ' => 'φ', + 'ᵡ' => 'χ', + 'ᵢ' => 'i', + 'ᵣ' => 'r', + 'ᵤ' => 'u', + 'ᵥ' => 'v', + 'ᵦ' => 'β', + 'ᵧ' => 'γ', + 'ᵨ' => 'ρ', + 'ᵩ' => 'φ', + 'ᵪ' => 'χ', + 'ᵸ' => 'н', + 'ᶛ' => 'ɒ', + 'ᶜ' => 'c', + 'ᶝ' => 'ɕ', + 'ᶞ' => 'ð', + 'ᶟ' => 'ɜ', + 'ᶠ' => 'f', + 'ᶡ' => 'ɟ', + 'ᶢ' => 'ɡ', + 'ᶣ' => 'ɥ', + 'ᶤ' => 'ɨ', + 'ᶥ' => 'ɩ', + 'ᶦ' => 'ɪ', + 'ᶧ' => 'ᵻ', + 'ᶨ' => 'ʝ', + 'ᶩ' => 'ɭ', + 'ᶪ' => 'ᶅ', + 'ᶫ' => 'ʟ', + 'ᶬ' => 'ɱ', + 'ᶭ' => 'ɰ', + 'ᶮ' => 'ɲ', + 'ᶯ' => 'ɳ', + 'ᶰ' => 'ɴ', + 'ᶱ' => 'ɵ', + 'ᶲ' => 'ɸ', + 'ᶳ' => 'ʂ', + 'ᶴ' => 'ʃ', + 'ᶵ' => 'ƫ', + 'ᶶ' => 'ʉ', + 'ᶷ' => 'ʊ', + 'ᶸ' => 'ᴜ', + 'ᶹ' => 'ʋ', + 'ᶺ' => 'ʌ', + 'ᶻ' => 'z', + 'ᶼ' => 'ʐ', + 'ᶽ' => 'ʑ', + 'ᶾ' => 'ʒ', + 'ᶿ' => 'θ', + 'ẚ' => 'aʾ', + 'ẛ' => 'ṡ', + '᾽' => ' ̓', + '᾿' => ' ̓', + '῀' => ' ͂', + '῁' => ' ̈͂', + '῍' => ' ̓̀', + '῎' => ' ̓́', + '῏' => ' ̓͂', + '῝' => ' ̔̀', + '῞' => ' ̔́', + '῟' => ' ̔͂', + '῭' => ' ̈̀', + '΅' => ' ̈́', + '´' => ' ́', + '῾' => ' ̔', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '‑' => '‐', + '‗' => ' ̳', + '․' => '.', + '‥' => '..', + '…' => '...', + ' ' => ' ', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '‾' => ' ̅', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '′′′′', + ' ' => ' ', + '⁰' => '0', + 'ⁱ' => 'i', + '⁴' => '4', + '⁵' => '5', + '⁶' => '6', + '⁷' => '7', + '⁸' => '8', + '⁹' => '9', + '⁺' => '+', + '⁻' => '−', + '⁼' => '=', + '⁽' => '(', + '⁾' => ')', + 'ⁿ' => 'n', + '₀' => '0', + '₁' => '1', + '₂' => '2', + '₃' => '3', + '₄' => '4', + '₅' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '₊' => '+', + '₋' => '−', + '₌' => '=', + '₍' => '(', + '₎' => ')', + 'ₐ' => 'a', + 'ₑ' => 'e', + 'ₒ' => 'o', + 'ₓ' => 'x', + 'ₔ' => 'ə', + 'ₕ' => 'h', + 'ₖ' => 'k', + 'ₗ' => 'l', + 'ₘ' => 'm', + 'ₙ' => 'n', + 'ₚ' => 'p', + 'ₛ' => 's', + 'ₜ' => 't', + '₨' => 'Rs', + '℀' => 'a/c', + '℁' => 'a/s', + 'ℂ' => 'C', + '℃' => '°C', + '℅' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Ɛ', + '℉' => '°F', + 'ℊ' => 'g', + 'ℋ' => 'H', + 'ℌ' => 'H', + 'ℍ' => 'H', + 'ℎ' => 'h', + 'ℏ' => 'ħ', + 'ℐ' => 'I', + 'ℑ' => 'I', + 'ℒ' => 'L', + 'ℓ' => 'l', + 'ℕ' => 'N', + '№' => 'No', + 'ℙ' => 'P', + 'ℚ' => 'Q', + 'ℛ' => 'R', + 'ℜ' => 'R', + 'ℝ' => 'R', + '℠' => 'SM', + '℡' => 'TEL', + '™' => 'TM', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'ℭ' => 'C', + 'ℯ' => 'e', + 'ℰ' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'ℴ' => 'o', + 'ℵ' => 'א', + 'ℶ' => 'ב', + 'ℷ' => 'ג', + 'ℸ' => 'ד', + 'ℹ' => 'i', + '℻' => 'FAX', + 'ℼ' => 'π', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'ℿ' => 'Π', + '⅀' => '∑', + 'ⅅ' => 'D', + 'ⅆ' => 'd', + 'ⅇ' => 'e', + 'ⅈ' => 'i', + 'ⅉ' => 'j', + '⅐' => '1⁄7', + '⅑' => '1⁄9', + '⅒' => '1⁄10', + '⅓' => '1⁄3', + '⅔' => '2⁄3', + '⅕' => '1⁄5', + '⅖' => '2⁄5', + '⅗' => '3⁄5', + '⅘' => '4⁄5', + '⅙' => '1⁄6', + '⅚' => '5⁄6', + '⅛' => '1⁄8', + '⅜' => '3⁄8', + '⅝' => '5⁄8', + '⅞' => '7⁄8', + '⅟' => '1⁄', + 'Ⅰ' => 'I', + 'Ⅱ' => 'II', + 'Ⅲ' => 'III', + 'Ⅳ' => 'IV', + 'Ⅴ' => 'V', + 'Ⅵ' => 'VI', + 'Ⅶ' => 'VII', + 'Ⅷ' => 'VIII', + 'Ⅸ' => 'IX', + 'Ⅹ' => 'X', + 'Ⅺ' => 'XI', + 'Ⅻ' => 'XII', + 'Ⅼ' => 'L', + 'Ⅽ' => 'C', + 'Ⅾ' => 'D', + 'Ⅿ' => 'M', + 'ⅰ' => 'i', + 'ⅱ' => 'ii', + 'ⅲ' => 'iii', + 'ⅳ' => 'iv', + 'ⅴ' => 'v', + 'ⅵ' => 'vi', + 'ⅶ' => 'vii', + 'ⅷ' => 'viii', + 'ⅸ' => 'ix', + 'ⅹ' => 'x', + 'ⅺ' => 'xi', + 'ⅻ' => 'xii', + 'ⅼ' => 'l', + 'ⅽ' => 'c', + 'ⅾ' => 'd', + 'ⅿ' => 'm', + '↉' => '0⁄3', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + '①' => '1', + '②' => '2', + '③' => '3', + '④' => '4', + '⑤' => '5', + '⑥' => '6', + '⑦' => '7', + '⑧' => '8', + '⑨' => '9', + '⑩' => '10', + '⑪' => '11', + '⑫' => '12', + '⑬' => '13', + '⑭' => '14', + '⑮' => '15', + '⑯' => '16', + '⑰' => '17', + '⑱' => '18', + '⑲' => '19', + '⑳' => '20', + '⑴' => '(1)', + '⑵' => '(2)', + '⑶' => '(3)', + '⑷' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + '⑻' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + '⑿' => '(12)', + '⒀' => '(13)', + '⒁' => '(14)', + '⒂' => '(15)', + '⒃' => '(16)', + '⒄' => '(17)', + '⒅' => '(18)', + '⒆' => '(19)', + '⒇' => '(20)', + '⒈' => '1.', + '⒉' => '2.', + '⒊' => '3.', + '⒋' => '4.', + '⒌' => '5.', + '⒍' => '6.', + '⒎' => '7.', + '⒏' => '8.', + '⒐' => '9.', + '⒑' => '10.', + '⒒' => '11.', + '⒓' => '12.', + '⒔' => '13.', + '⒕' => '14.', + '⒖' => '15.', + '⒗' => '16.', + '⒘' => '17.', + '⒙' => '18.', + '⒚' => '19.', + '⒛' => '20.', + '⒜' => '(a)', + '⒝' => '(b)', + '⒞' => '(c)', + '⒟' => '(d)', + '⒠' => '(e)', + '⒡' => '(f)', + '⒢' => '(g)', + '⒣' => '(h)', + '⒤' => '(i)', + '⒥' => '(j)', + '⒦' => '(k)', + '⒧' => '(l)', + '⒨' => '(m)', + '⒩' => '(n)', + '⒪' => '(o)', + '⒫' => '(p)', + '⒬' => '(q)', + '⒭' => '(r)', + '⒮' => '(s)', + '⒯' => '(t)', + '⒰' => '(u)', + '⒱' => '(v)', + '⒲' => '(w)', + '⒳' => '(x)', + '⒴' => '(y)', + '⒵' => '(z)', + 'Ⓐ' => 'A', + 'Ⓑ' => 'B', + 'Ⓒ' => 'C', + 'Ⓓ' => 'D', + 'Ⓔ' => 'E', + 'Ⓕ' => 'F', + 'Ⓖ' => 'G', + 'Ⓗ' => 'H', + 'Ⓘ' => 'I', + 'Ⓙ' => 'J', + 'Ⓚ' => 'K', + 'Ⓛ' => 'L', + 'Ⓜ' => 'M', + 'Ⓝ' => 'N', + 'Ⓞ' => 'O', + 'Ⓟ' => 'P', + 'Ⓠ' => 'Q', + 'Ⓡ' => 'R', + 'Ⓢ' => 'S', + 'Ⓣ' => 'T', + 'Ⓤ' => 'U', + 'Ⓥ' => 'V', + 'Ⓦ' => 'W', + 'Ⓧ' => 'X', + 'Ⓨ' => 'Y', + 'Ⓩ' => 'Z', + 'ⓐ' => 'a', + 'ⓑ' => 'b', + 'ⓒ' => 'c', + 'ⓓ' => 'd', + 'ⓔ' => 'e', + 'ⓕ' => 'f', + 'ⓖ' => 'g', + 'ⓗ' => 'h', + 'ⓘ' => 'i', + 'ⓙ' => 'j', + 'ⓚ' => 'k', + 'ⓛ' => 'l', + 'ⓜ' => 'm', + 'ⓝ' => 'n', + 'ⓞ' => 'o', + 'ⓟ' => 'p', + 'ⓠ' => 'q', + 'ⓡ' => 'r', + 'ⓢ' => 's', + 'ⓣ' => 't', + 'ⓤ' => 'u', + 'ⓥ' => 'v', + 'ⓦ' => 'w', + 'ⓧ' => 'x', + 'ⓨ' => 'y', + 'ⓩ' => 'z', + '⓪' => '0', + '⨌' => '∫∫∫∫', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + 'ⱼ' => 'j', + 'ⱽ' => 'V', + 'ⵯ' => 'ⵡ', + '⺟' => '母', + '⻳' => '龟', + '⼀' => '一', + '⼁' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => '乙', + '⼅' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => '儿', + '⼊' => '入', + '⼋' => '八', + '⼌' => '冂', + '⼍' => '冖', + '⼎' => '冫', + '⼏' => '几', + '⼐' => '凵', + '⼑' => '刀', + '⼒' => '力', + '⼓' => '勹', + '⼔' => '匕', + '⼕' => '匚', + '⼖' => '匸', + '⼗' => '十', + '⼘' => '卜', + '⼙' => '卩', + '⼚' => '厂', + '⼛' => '厶', + '⼜' => '又', + '⼝' => '口', + '⼞' => '囗', + '⼟' => '土', + '⼠' => '士', + '⼡' => '夂', + '⼢' => '夊', + '⼣' => '夕', + '⼤' => '大', + '⼥' => '女', + '⼦' => '子', + '⼧' => '宀', + '⼨' => '寸', + '⼩' => '小', + '⼪' => '尢', + '⼫' => '尸', + '⼬' => '屮', + '⼭' => '山', + '⼮' => '巛', + '⼯' => '工', + '⼰' => '己', + '⼱' => '巾', + '⼲' => '干', + '⼳' => '幺', + '⼴' => '广', + '⼵' => '廴', + '⼶' => '廾', + '⼷' => '弋', + '⼸' => '弓', + '⼹' => '彐', + '⼺' => '彡', + '⼻' => '彳', + '⼼' => '心', + '⼽' => '戈', + '⼾' => '戶', + '⼿' => '手', + '⽀' => '支', + '⽁' => '攴', + '⽂' => '文', + '⽃' => '斗', + '⽄' => '斤', + '⽅' => '方', + '⽆' => '无', + '⽇' => '日', + '⽈' => '曰', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => '止', + '⽍' => '歹', + '⽎' => '殳', + '⽏' => '毋', + '⽐' => '比', + '⽑' => '毛', + '⽒' => '氏', + '⽓' => '气', + '⽔' => '水', + '⽕' => '火', + '⽖' => '爪', + '⽗' => '父', + '⽘' => '爻', + '⽙' => '爿', + '⽚' => '片', + '⽛' => '牙', + '⽜' => '牛', + '⽝' => '犬', + '⽞' => '玄', + '⽟' => '玉', + '⽠' => '瓜', + '⽡' => '瓦', + '⽢' => '甘', + '⽣' => '生', + '⽤' => '用', + '⽥' => '田', + '⽦' => '疋', + '⽧' => '疒', + '⽨' => '癶', + '⽩' => '白', + '⽪' => '皮', + '⽫' => '皿', + '⽬' => '目', + '⽭' => '矛', + '⽮' => '矢', + '⽯' => '石', + '⽰' => '示', + '⽱' => '禸', + '⽲' => '禾', + '⽳' => '穴', + '⽴' => '立', + '⽵' => '竹', + '⽶' => '米', + '⽷' => '糸', + '⽸' => '缶', + '⽹' => '网', + '⽺' => '羊', + '⽻' => '羽', + '⽼' => '老', + '⽽' => '而', + '⽾' => '耒', + '⽿' => '耳', + '⾀' => '聿', + '⾁' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + '⾅' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => '虍', + '⾍' => '虫', + '⾎' => '血', + '⾏' => '行', + '⾐' => '衣', + '⾑' => '襾', + '⾒' => '見', + '⾓' => '角', + '⾔' => '言', + '⾕' => '谷', + '⾖' => '豆', + '⾗' => '豕', + '⾘' => '豸', + '⾙' => '貝', + '⾚' => '赤', + '⾛' => '走', + '⾜' => '足', + '⾝' => '身', + '⾞' => '車', + '⾟' => '辛', + '⾠' => '辰', + '⾡' => '辵', + '⾢' => '邑', + '⾣' => '酉', + '⾤' => '釆', + '⾥' => '里', + '⾦' => '金', + '⾧' => '長', + '⾨' => '門', + '⾩' => '阜', + '⾪' => '隶', + '⾫' => '隹', + '⾬' => '雨', + '⾭' => '靑', + '⾮' => '非', + '⾯' => '面', + '⾰' => '革', + '⾱' => '韋', + '⾲' => '韭', + '⾳' => '音', + '⾴' => '頁', + '⾵' => '風', + '⾶' => '飛', + '⾷' => '食', + '⾸' => '首', + '⾹' => '香', + '⾺' => '馬', + '⾻' => '骨', + '⾼' => '高', + '⾽' => '髟', + '⾾' => '鬥', + '⾿' => '鬯', + '⿀' => '鬲', + '⿁' => '鬼', + '⿂' => '魚', + '⿃' => '鳥', + '⿄' => '鹵', + '⿅' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => '黍', + '⿊' => '黑', + '⿋' => '黹', + '⿌' => '黽', + '⿍' => '鼎', + '⿎' => '鼓', + '⿏' => '鼠', + '⿐' => '鼻', + '⿑' => '齊', + '⿒' => '齒', + '⿓' => '龍', + '⿔' => '龜', + '⿕' => '龠', + ' ' => ' ', + '〶' => '〒', + '〸' => '十', + '〹' => '卄', + '〺' => '卅', + '゛' => ' ゙', + '゜' => ' ゚', + 'ゟ' => 'より', + 'ヿ' => 'コト', + 'ㄱ' => 'ᄀ', + 'ㄲ' => 'ᄁ', + 'ㄳ' => 'ᆪ', + 'ㄴ' => 'ᄂ', + 'ㄵ' => 'ᆬ', + 'ㄶ' => 'ᆭ', + 'ㄷ' => 'ᄃ', + 'ㄸ' => 'ᄄ', + 'ㄹ' => 'ᄅ', + 'ㄺ' => 'ᆰ', + 'ㄻ' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ㄿ' => 'ᆵ', + 'ㅀ' => 'ᄚ', + 'ㅁ' => 'ᄆ', + 'ㅂ' => 'ᄇ', + 'ㅃ' => 'ᄈ', + 'ㅄ' => 'ᄡ', + 'ㅅ' => 'ᄉ', + 'ㅆ' => 'ᄊ', + 'ㅇ' => 'ᄋ', + 'ㅈ' => 'ᄌ', + 'ㅉ' => 'ᄍ', + 'ㅊ' => 'ᄎ', + 'ㅋ' => 'ᄏ', + 'ㅌ' => 'ᄐ', + 'ㅍ' => 'ᄑ', + 'ㅎ' => 'ᄒ', + 'ㅏ' => 'ᅡ', + 'ㅐ' => 'ᅢ', + 'ㅑ' => 'ᅣ', + 'ㅒ' => 'ᅤ', + 'ㅓ' => 'ᅥ', + 'ㅔ' => 'ᅦ', + 'ㅕ' => 'ᅧ', + 'ㅖ' => 'ᅨ', + 'ㅗ' => 'ᅩ', + 'ㅘ' => 'ᅪ', + 'ㅙ' => 'ᅫ', + 'ㅚ' => 'ᅬ', + 'ㅛ' => 'ᅭ', + 'ㅜ' => 'ᅮ', + 'ㅝ' => 'ᅯ', + 'ㅞ' => 'ᅰ', + 'ㅟ' => 'ᅱ', + 'ㅠ' => 'ᅲ', + 'ㅡ' => 'ᅳ', + 'ㅢ' => 'ᅴ', + 'ㅣ' => 'ᅵ', + 'ㅤ' => 'ᅠ', + 'ㅥ' => 'ᄔ', + 'ㅦ' => 'ᄕ', + 'ㅧ' => 'ᇇ', + 'ㅨ' => 'ᇈ', + 'ㅩ' => 'ᇌ', + 'ㅪ' => 'ᇎ', + 'ㅫ' => 'ᇓ', + 'ㅬ' => 'ᇗ', + 'ㅭ' => 'ᇙ', + 'ㅮ' => 'ᄜ', + 'ㅯ' => 'ᇝ', + 'ㅰ' => 'ᇟ', + 'ㅱ' => 'ᄝ', + 'ㅲ' => 'ᄞ', + 'ㅳ' => 'ᄠ', + 'ㅴ' => 'ᄢ', + 'ㅵ' => 'ᄣ', + 'ㅶ' => 'ᄧ', + 'ㅷ' => 'ᄩ', + 'ㅸ' => 'ᄫ', + 'ㅹ' => 'ᄬ', + 'ㅺ' => 'ᄭ', + 'ㅻ' => 'ᄮ', + 'ㅼ' => 'ᄯ', + 'ㅽ' => 'ᄲ', + 'ㅾ' => 'ᄶ', + 'ㅿ' => 'ᅀ', + 'ㆀ' => 'ᅇ', + 'ㆁ' => 'ᅌ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'ᅗ', + 'ㆅ' => 'ᅘ', + 'ㆆ' => 'ᅙ', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ㆍ' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㆒' => '一', + '㆓' => '二', + '㆔' => '三', + '㆕' => '四', + '㆖' => '上', + '㆗' => '中', + '㆘' => '下', + '㆙' => '甲', + '㆚' => '乙', + '㆛' => '丙', + '㆜' => '丁', + '㆝' => '天', + '㆞' => '地', + '㆟' => '人', + '㈀' => '(ᄀ)', + '㈁' => '(ᄂ)', + '㈂' => '(ᄃ)', + '㈃' => '(ᄅ)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(ᄋ)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(ᄏ)', + '㈋' => '(ᄐ)', + '㈌' => '(ᄑ)', + '㈍' => '(ᄒ)', + '㈎' => '(가)', + '㈏' => '(나)', + '㈐' => '(다)', + '㈑' => '(라)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(아)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(카)', + '㈙' => '(타)', + '㈚' => '(파)', + '㈛' => '(하)', + '㈜' => '(주)', + '㈝' => '(오전)', + '㈞' => '(오후)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(四)', + '㈤' => '(五)', + '㈥' => '(六)', + '㈦' => '(七)', + '㈧' => '(八)', + '㈨' => '(九)', + '㈩' => '(十)', + '㈪' => '(月)', + '㈫' => '(火)', + '㈬' => '(水)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(日)', + '㈱' => '(株)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(名)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(祝)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(学)', + '㈼' => '(監)', + '㈽' => '(企)', + '㈾' => '(資)', + '㈿' => '(協)', + '㉀' => '(祭)', + '㉁' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => '問', + '㉅' => '幼', + '㉆' => '文', + '㉇' => '箏', + '㉐' => 'PTE', + '㉑' => '21', + '㉒' => '22', + '㉓' => '23', + '㉔' => '24', + '㉕' => '25', + '㉖' => '26', + '㉗' => '27', + '㉘' => '28', + '㉙' => '29', + '㉚' => '30', + '㉛' => '31', + '㉜' => '32', + '㉝' => '33', + '㉞' => '34', + '㉟' => '35', + '㉠' => 'ᄀ', + '㉡' => 'ᄂ', + '㉢' => 'ᄃ', + '㉣' => 'ᄅ', + '㉤' => 'ᄆ', + '㉥' => 'ᄇ', + '㉦' => 'ᄉ', + '㉧' => 'ᄋ', + '㉨' => 'ᄌ', + '㉩' => 'ᄎ', + '㉪' => 'ᄏ', + '㉫' => 'ᄐ', + '㉬' => 'ᄑ', + '㉭' => 'ᄒ', + '㉮' => '가', + '㉯' => '나', + '㉰' => '다', + '㉱' => '라', + '㉲' => '마', + '㉳' => '바', + '㉴' => '사', + '㉵' => '아', + '㉶' => '자', + '㉷' => '차', + '㉸' => '카', + '㉹' => '타', + '㉺' => '파', + '㉻' => '하', + '㉼' => '참고', + '㉽' => '주의', + '㉾' => '우', + '㊀' => '一', + '㊁' => '二', + '㊂' => '三', + '㊃' => '四', + '㊄' => '五', + '㊅' => '六', + '㊆' => '七', + '㊇' => '八', + '㊈' => '九', + '㊉' => '十', + '㊊' => '月', + '㊋' => '火', + '㊌' => '水', + '㊍' => '木', + '㊎' => '金', + '㊏' => '土', + '㊐' => '日', + '㊑' => '株', + '㊒' => '有', + '㊓' => '社', + '㊔' => '名', + '㊕' => '特', + '㊖' => '財', + '㊗' => '祝', + '㊘' => '労', + '㊙' => '秘', + '㊚' => '男', + '㊛' => '女', + '㊜' => '適', + '㊝' => '優', + '㊞' => '印', + '㊟' => '注', + '㊠' => '項', + '㊡' => '休', + '㊢' => '写', + '㊣' => '正', + '㊤' => '上', + '㊥' => '中', + '㊦' => '下', + '㊧' => '左', + '㊨' => '右', + '㊩' => '医', + '㊪' => '宗', + '㊫' => '学', + '㊬' => '監', + '㊭' => '企', + '㊮' => '資', + '㊯' => '協', + '㊰' => '夜', + '㊱' => '36', + '㊲' => '37', + '㊳' => '38', + '㊴' => '39', + '㊵' => '40', + '㊶' => '41', + '㊷' => '42', + '㊸' => '43', + '㊹' => '44', + '㊺' => '45', + '㊻' => '46', + '㊼' => '47', + '㊽' => '48', + '㊾' => '49', + '㊿' => '50', + '㋀' => '1月', + '㋁' => '2月', + '㋂' => '3月', + '㋃' => '4月', + '㋄' => '5月', + '㋅' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + '㋋' => '12月', + '㋌' => 'Hg', + '㋍' => 'erg', + '㋎' => 'eV', + '㋏' => 'LTD', + '㋐' => 'ア', + '㋑' => 'イ', + '㋒' => 'ウ', + '㋓' => 'エ', + '㋔' => 'オ', + '㋕' => 'カ', + '㋖' => 'キ', + '㋗' => 'ク', + '㋘' => 'ケ', + '㋙' => 'コ', + '㋚' => 'サ', + '㋛' => 'シ', + '㋜' => 'ス', + '㋝' => 'セ', + '㋞' => 'ソ', + '㋟' => 'タ', + '㋠' => 'チ', + '㋡' => 'ツ', + '㋢' => 'テ', + '㋣' => 'ト', + '㋤' => 'ナ', + '㋥' => 'ニ', + '㋦' => 'ヌ', + '㋧' => 'ネ', + '㋨' => 'ノ', + '㋩' => 'ハ', + '㋪' => 'ヒ', + '㋫' => 'フ', + '㋬' => 'ヘ', + '㋭' => 'ホ', + '㋮' => 'マ', + '㋯' => 'ミ', + '㋰' => 'ム', + '㋱' => 'メ', + '㋲' => 'モ', + '㋳' => 'ヤ', + '㋴' => 'ユ', + '㋵' => 'ヨ', + '㋶' => 'ラ', + '㋷' => 'リ', + '㋸' => 'ル', + '㋹' => 'レ', + '㋺' => 'ロ', + '㋻' => 'ワ', + '㋼' => 'ヰ', + '㋽' => 'ヱ', + '㋾' => 'ヲ', + '㋿' => '令和', + '㌀' => 'アパート', + '㌁' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インチ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + '㌍' => 'カロリー', + '㌎' => 'ガロン', + '㌏' => 'ガンマ', + '㌐' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローネ', + '㌜' => 'ケース', + '㌝' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンチーム', + '㌡' => 'シリング', + '㌢' => 'センチ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ハイツ', + '㌫' => 'パーセント', + '㌬' => 'パーツ', + '㌭' => 'バーレル', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + '㍀' => 'ポンド', + '㍁' => 'ホール', + '㍂' => 'ホーン', + '㍃' => 'マイクロ', + '㍄' => 'マイル', + '㍅' => 'マッハ', + '㍆' => 'マルク', + '㍇' => 'マンション', + '㍈' => 'ミクロン', + '㍉' => 'ミリ', + '㍊' => 'ミリバール', + '㍋' => 'メガ', + '㍌' => 'メガトン', + '㍍' => 'メートル', + '㍎' => 'ヤード', + '㍏' => 'ヤール', + '㍐' => 'ユアン', + '㍑' => 'リットル', + '㍒' => 'リラ', + '㍓' => 'ルピー', + '㍔' => 'ルーブル', + '㍕' => 'レム', + '㍖' => 'レントゲン', + '㍗' => 'ワット', + '㍘' => '0点', + '㍙' => '1点', + '㍚' => '2点', + '㍛' => '3点', + '㍜' => '4点', + '㍝' => '5点', + '㍞' => '6点', + '㍟' => '7点', + '㍠' => '8点', + '㍡' => '9点', + '㍢' => '10点', + '㍣' => '11点', + '㍤' => '12点', + '㍥' => '13点', + '㍦' => '14点', + '㍧' => '15点', + '㍨' => '16点', + '㍩' => '17点', + '㍪' => '18点', + '㍫' => '19点', + '㍬' => '20点', + '㍭' => '21点', + '㍮' => '22点', + '㍯' => '23点', + '㍰' => '24点', + '㍱' => 'hPa', + '㍲' => 'da', + '㍳' => 'AU', + '㍴' => 'bar', + '㍵' => 'oV', + '㍶' => 'pc', + '㍷' => 'dm', + '㍸' => 'dm2', + '㍹' => 'dm3', + '㍺' => 'IU', + '㍻' => '平成', + '㍼' => '昭和', + '㍽' => '大正', + '㍾' => '明治', + '㍿' => '株式会社', + '㎀' => 'pA', + '㎁' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + '㎍' => 'μg', + '㎎' => 'mg', + '㎏' => 'kg', + '㎐' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μl', + '㎖' => 'ml', + '㎗' => 'dl', + '㎘' => 'kl', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + '㎝' => 'cm', + '㎞' => 'km', + '㎟' => 'mm2', + '㎠' => 'cm2', + '㎡' => 'm2', + '㎢' => 'km2', + '㎣' => 'mm3', + '㎤' => 'cm3', + '㎥' => 'm3', + '㎦' => 'km3', + '㎧' => 'm∕s', + '㎨' => 'm∕s2', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s2', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + '㏀' => 'kΩ', + '㏁' => 'MΩ', + '㏂' => 'a.m.', + '㏃' => 'Bq', + '㏄' => 'cc', + '㏅' => 'cd', + '㏆' => 'C∕kg', + '㏇' => 'Co.', + '㏈' => 'dB', + '㏉' => 'Gy', + '㏊' => 'ha', + '㏋' => 'HP', + '㏌' => 'in', + '㏍' => 'KK', + '㏎' => 'KM', + '㏏' => 'kt', + '㏐' => 'lm', + '㏑' => 'ln', + '㏒' => 'log', + '㏓' => 'lx', + '㏔' => 'mb', + '㏕' => 'mil', + '㏖' => 'mol', + '㏗' => 'PH', + '㏘' => 'p.m.', + '㏙' => 'PPM', + '㏚' => 'PR', + '㏛' => 'sr', + '㏜' => 'Sv', + '㏝' => 'Wb', + '㏞' => 'V∕m', + '㏟' => 'A∕m', + '㏠' => '1日', + '㏡' => '2日', + '㏢' => '3日', + '㏣' => '4日', + '㏤' => '5日', + '㏥' => '6日', + '㏦' => '7日', + '㏧' => '8日', + '㏨' => '9日', + '㏩' => '10日', + '㏪' => '11日', + '㏫' => '12日', + '㏬' => '13日', + '㏭' => '14日', + '㏮' => '15日', + '㏯' => '16日', + '㏰' => '17日', + '㏱' => '18日', + '㏲' => '19日', + '㏳' => '20日', + '㏴' => '21日', + '㏵' => '22日', + '㏶' => '23日', + '㏷' => '24日', + '㏸' => '25日', + '㏹' => '26日', + '㏺' => '27日', + '㏻' => '28日', + '㏼' => '29日', + '㏽' => '30日', + '㏾' => '31日', + '㏿' => 'gal', + 'ꚜ' => 'ъ', + 'ꚝ' => 'ь', + 'ꝰ' => 'ꝯ', + 'ꟸ' => 'Ħ', + 'ꟹ' => 'œ', + 'ꭜ' => 'ꜧ', + 'ꭝ' => 'ꬷ', + 'ꭞ' => 'ɫ', + 'ꭟ' => 'ꭒ', + 'ꭩ' => 'ʍ', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', + 'ﬠ' => 'ע', + 'ﬡ' => 'א', + 'ﬢ' => 'ד', + 'ﬣ' => 'ה', + 'ﬤ' => 'כ', + 'ﬥ' => 'ל', + 'ﬦ' => 'ם', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ﭏ' => 'אל', + 'ﭐ' => 'ٱ', + 'ﭑ' => 'ٱ', + 'ﭒ' => 'ٻ', + 'ﭓ' => 'ٻ', + 'ﭔ' => 'ٻ', + 'ﭕ' => 'ٻ', + 'ﭖ' => 'پ', + 'ﭗ' => 'پ', + 'ﭘ' => 'پ', + 'ﭙ' => 'پ', + 'ﭚ' => 'ڀ', + 'ﭛ' => 'ڀ', + 'ﭜ' => 'ڀ', + 'ﭝ' => 'ڀ', + 'ﭞ' => 'ٺ', + 'ﭟ' => 'ٺ', + 'ﭠ' => 'ٺ', + 'ﭡ' => 'ٺ', + 'ﭢ' => 'ٿ', + 'ﭣ' => 'ٿ', + 'ﭤ' => 'ٿ', + 'ﭥ' => 'ٿ', + 'ﭦ' => 'ٹ', + 'ﭧ' => 'ٹ', + 'ﭨ' => 'ٹ', + 'ﭩ' => 'ٹ', + 'ﭪ' => 'ڤ', + 'ﭫ' => 'ڤ', + 'ﭬ' => 'ڤ', + 'ﭭ' => 'ڤ', + 'ﭮ' => 'ڦ', + 'ﭯ' => 'ڦ', + 'ﭰ' => 'ڦ', + 'ﭱ' => 'ڦ', + 'ﭲ' => 'ڄ', + 'ﭳ' => 'ڄ', + 'ﭴ' => 'ڄ', + 'ﭵ' => 'ڄ', + 'ﭶ' => 'ڃ', + 'ﭷ' => 'ڃ', + 'ﭸ' => 'ڃ', + 'ﭹ' => 'ڃ', + 'ﭺ' => 'چ', + 'ﭻ' => 'چ', + 'ﭼ' => 'چ', + 'ﭽ' => 'چ', + 'ﭾ' => 'ڇ', + 'ﭿ' => 'ڇ', + 'ﮀ' => 'ڇ', + 'ﮁ' => 'ڇ', + 'ﮂ' => 'ڍ', + 'ﮃ' => 'ڍ', + 'ﮄ' => 'ڌ', + 'ﮅ' => 'ڌ', + 'ﮆ' => 'ڎ', + 'ﮇ' => 'ڎ', + 'ﮈ' => 'ڈ', + 'ﮉ' => 'ڈ', + 'ﮊ' => 'ژ', + 'ﮋ' => 'ژ', + 'ﮌ' => 'ڑ', + 'ﮍ' => 'ڑ', + 'ﮎ' => 'ک', + 'ﮏ' => 'ک', + 'ﮐ' => 'ک', + 'ﮑ' => 'ک', + 'ﮒ' => 'گ', + 'ﮓ' => 'گ', + 'ﮔ' => 'گ', + 'ﮕ' => 'گ', + 'ﮖ' => 'ڳ', + 'ﮗ' => 'ڳ', + 'ﮘ' => 'ڳ', + 'ﮙ' => 'ڳ', + 'ﮚ' => 'ڱ', + 'ﮛ' => 'ڱ', + 'ﮜ' => 'ڱ', + 'ﮝ' => 'ڱ', + 'ﮞ' => 'ں', + 'ﮟ' => 'ں', + 'ﮠ' => 'ڻ', + 'ﮡ' => 'ڻ', + 'ﮢ' => 'ڻ', + 'ﮣ' => 'ڻ', + 'ﮤ' => 'ۀ', + 'ﮥ' => 'ۀ', + 'ﮦ' => 'ہ', + 'ﮧ' => 'ہ', + 'ﮨ' => 'ہ', + 'ﮩ' => 'ہ', + 'ﮪ' => 'ھ', + 'ﮫ' => 'ھ', + 'ﮬ' => 'ھ', + 'ﮭ' => 'ھ', + 'ﮮ' => 'ے', + 'ﮯ' => 'ے', + 'ﮰ' => 'ۓ', + 'ﮱ' => 'ۓ', + 'ﯓ' => 'ڭ', + 'ﯔ' => 'ڭ', + 'ﯕ' => 'ڭ', + 'ﯖ' => 'ڭ', + 'ﯗ' => 'ۇ', + 'ﯘ' => 'ۇ', + 'ﯙ' => 'ۆ', + 'ﯚ' => 'ۆ', + 'ﯛ' => 'ۈ', + 'ﯜ' => 'ۈ', + 'ﯝ' => 'ۇٴ', + 'ﯞ' => 'ۋ', + 'ﯟ' => 'ۋ', + 'ﯠ' => 'ۅ', + 'ﯡ' => 'ۅ', + 'ﯢ' => 'ۉ', + 'ﯣ' => 'ۉ', + 'ﯤ' => 'ې', + 'ﯥ' => 'ې', + 'ﯦ' => 'ې', + 'ﯧ' => 'ې', + 'ﯨ' => 'ى', + 'ﯩ' => 'ى', + 'ﯪ' => 'ئا', + 'ﯫ' => 'ئا', + 'ﯬ' => 'ئە', + 'ﯭ' => 'ئە', + 'ﯮ' => 'ئو', + 'ﯯ' => 'ئو', + 'ﯰ' => 'ئۇ', + 'ﯱ' => 'ئۇ', + 'ﯲ' => 'ئۆ', + 'ﯳ' => 'ئۆ', + 'ﯴ' => 'ئۈ', + 'ﯵ' => 'ئۈ', + 'ﯶ' => 'ئې', + 'ﯷ' => 'ئې', + 'ﯸ' => 'ئې', + 'ﯹ' => 'ئى', + 'ﯺ' => 'ئى', + 'ﯻ' => 'ئى', + 'ﯼ' => 'ی', + 'ﯽ' => 'ی', + 'ﯾ' => 'ی', + 'ﯿ' => 'ی', + 'ﰀ' => 'ئج', + 'ﰁ' => 'ئح', + 'ﰂ' => 'ئم', + 'ﰃ' => 'ئى', + 'ﰄ' => 'ئي', + 'ﰅ' => 'بج', + 'ﰆ' => 'بح', + 'ﰇ' => 'بخ', + 'ﰈ' => 'بم', + 'ﰉ' => 'بى', + 'ﰊ' => 'بي', + 'ﰋ' => 'تج', + 'ﰌ' => 'تح', + 'ﰍ' => 'تخ', + 'ﰎ' => 'تم', + 'ﰏ' => 'تى', + 'ﰐ' => 'تي', + 'ﰑ' => 'ثج', + 'ﰒ' => 'ثم', + 'ﰓ' => 'ثى', + 'ﰔ' => 'ثي', + 'ﰕ' => 'جح', + 'ﰖ' => 'جم', + 'ﰗ' => 'حج', + 'ﰘ' => 'حم', + 'ﰙ' => 'خج', + 'ﰚ' => 'خح', + 'ﰛ' => 'خم', + 'ﰜ' => 'سج', + 'ﰝ' => 'سح', + 'ﰞ' => 'سخ', + 'ﰟ' => 'سم', + 'ﰠ' => 'صح', + 'ﰡ' => 'صم', + 'ﰢ' => 'ضج', + 'ﰣ' => 'ضح', + 'ﰤ' => 'ضخ', + 'ﰥ' => 'ضم', + 'ﰦ' => 'طح', + 'ﰧ' => 'طم', + 'ﰨ' => 'ظم', + 'ﰩ' => 'عج', + 'ﰪ' => 'عم', + 'ﰫ' => 'غج', + 'ﰬ' => 'غم', + 'ﰭ' => 'فج', + 'ﰮ' => 'فح', + 'ﰯ' => 'فخ', + 'ﰰ' => 'فم', + 'ﰱ' => 'فى', + 'ﰲ' => 'في', + 'ﰳ' => 'قح', + 'ﰴ' => 'قم', + 'ﰵ' => 'قى', + 'ﰶ' => 'قي', + 'ﰷ' => 'كا', + 'ﰸ' => 'كج', + 'ﰹ' => 'كح', + 'ﰺ' => 'كخ', + 'ﰻ' => 'كل', + 'ﰼ' => 'كم', + 'ﰽ' => 'كى', + 'ﰾ' => 'كي', + 'ﰿ' => 'لج', + 'ﱀ' => 'لح', + 'ﱁ' => 'لخ', + 'ﱂ' => 'لم', + 'ﱃ' => 'لى', + 'ﱄ' => 'لي', + 'ﱅ' => 'مج', + 'ﱆ' => 'مح', + 'ﱇ' => 'مخ', + 'ﱈ' => 'مم', + 'ﱉ' => 'مى', + 'ﱊ' => 'مي', + 'ﱋ' => 'نج', + 'ﱌ' => 'نح', + 'ﱍ' => 'نخ', + 'ﱎ' => 'نم', + 'ﱏ' => 'نى', + 'ﱐ' => 'ني', + 'ﱑ' => 'هج', + 'ﱒ' => 'هم', + 'ﱓ' => 'هى', + 'ﱔ' => 'هي', + 'ﱕ' => 'يج', + 'ﱖ' => 'يح', + 'ﱗ' => 'يخ', + 'ﱘ' => 'يم', + 'ﱙ' => 'يى', + 'ﱚ' => 'يي', + 'ﱛ' => 'ذٰ', + 'ﱜ' => 'رٰ', + 'ﱝ' => 'ىٰ', + 'ﱞ' => ' ٌّ', + 'ﱟ' => ' ٍّ', + 'ﱠ' => ' َّ', + 'ﱡ' => ' ُّ', + 'ﱢ' => ' ِّ', + 'ﱣ' => ' ّٰ', + 'ﱤ' => 'ئر', + 'ﱥ' => 'ئز', + 'ﱦ' => 'ئم', + 'ﱧ' => 'ئن', + 'ﱨ' => 'ئى', + 'ﱩ' => 'ئي', + 'ﱪ' => 'بر', + 'ﱫ' => 'بز', + 'ﱬ' => 'بم', + 'ﱭ' => 'بن', + 'ﱮ' => 'بى', + 'ﱯ' => 'بي', + 'ﱰ' => 'تر', + 'ﱱ' => 'تز', + 'ﱲ' => 'تم', + 'ﱳ' => 'تن', + 'ﱴ' => 'تى', + 'ﱵ' => 'تي', + 'ﱶ' => 'ثر', + 'ﱷ' => 'ثز', + 'ﱸ' => 'ثم', + 'ﱹ' => 'ثن', + 'ﱺ' => 'ثى', + 'ﱻ' => 'ثي', + 'ﱼ' => 'فى', + 'ﱽ' => 'في', + 'ﱾ' => 'قى', + 'ﱿ' => 'قي', + 'ﲀ' => 'كا', + 'ﲁ' => 'كل', + 'ﲂ' => 'كم', + 'ﲃ' => 'كى', + 'ﲄ' => 'كي', + 'ﲅ' => 'لم', + 'ﲆ' => 'لى', + 'ﲇ' => 'لي', + 'ﲈ' => 'ما', + 'ﲉ' => 'مم', + 'ﲊ' => 'نر', + 'ﲋ' => 'نز', + 'ﲌ' => 'نم', + 'ﲍ' => 'نن', + 'ﲎ' => 'نى', + 'ﲏ' => 'ني', + 'ﲐ' => 'ىٰ', + 'ﲑ' => 'ير', + 'ﲒ' => 'يز', + 'ﲓ' => 'يم', + 'ﲔ' => 'ين', + 'ﲕ' => 'يى', + 'ﲖ' => 'يي', + 'ﲗ' => 'ئج', + 'ﲘ' => 'ئح', + 'ﲙ' => 'ئخ', + 'ﲚ' => 'ئم', + 'ﲛ' => 'ئه', + 'ﲜ' => 'بج', + 'ﲝ' => 'بح', + 'ﲞ' => 'بخ', + 'ﲟ' => 'بم', + 'ﲠ' => 'به', + 'ﲡ' => 'تج', + 'ﲢ' => 'تح', + 'ﲣ' => 'تخ', + 'ﲤ' => 'تم', + 'ﲥ' => 'ته', + 'ﲦ' => 'ثم', + 'ﲧ' => 'جح', + 'ﲨ' => 'جم', + 'ﲩ' => 'حج', + 'ﲪ' => 'حم', + 'ﲫ' => 'خج', + 'ﲬ' => 'خم', + 'ﲭ' => 'سج', + 'ﲮ' => 'سح', + 'ﲯ' => 'سخ', + 'ﲰ' => 'سم', + 'ﲱ' => 'صح', + 'ﲲ' => 'صخ', + 'ﲳ' => 'صم', + 'ﲴ' => 'ضج', + 'ﲵ' => 'ضح', + 'ﲶ' => 'ضخ', + 'ﲷ' => 'ضم', + 'ﲸ' => 'طح', + 'ﲹ' => 'ظم', + 'ﲺ' => 'عج', + 'ﲻ' => 'عم', + 'ﲼ' => 'غج', + 'ﲽ' => 'غم', + 'ﲾ' => 'فج', + 'ﲿ' => 'فح', + 'ﳀ' => 'فخ', + 'ﳁ' => 'فم', + 'ﳂ' => 'قح', + 'ﳃ' => 'قم', + 'ﳄ' => 'كج', + 'ﳅ' => 'كح', + 'ﳆ' => 'كخ', + 'ﳇ' => 'كل', + 'ﳈ' => 'كم', + 'ﳉ' => 'لج', + 'ﳊ' => 'لح', + 'ﳋ' => 'لخ', + 'ﳌ' => 'لم', + 'ﳍ' => 'له', + 'ﳎ' => 'مج', + 'ﳏ' => 'مح', + 'ﳐ' => 'مخ', + 'ﳑ' => 'مم', + 'ﳒ' => 'نج', + 'ﳓ' => 'نح', + 'ﳔ' => 'نخ', + 'ﳕ' => 'نم', + 'ﳖ' => 'نه', + 'ﳗ' => 'هج', + 'ﳘ' => 'هم', + 'ﳙ' => 'هٰ', + 'ﳚ' => 'يج', + 'ﳛ' => 'يح', + 'ﳜ' => 'يخ', + 'ﳝ' => 'يم', + 'ﳞ' => 'يه', + 'ﳟ' => 'ئم', + 'ﳠ' => 'ئه', + 'ﳡ' => 'بم', + 'ﳢ' => 'به', + 'ﳣ' => 'تم', + 'ﳤ' => 'ته', + 'ﳥ' => 'ثم', + 'ﳦ' => 'ثه', + 'ﳧ' => 'سم', + 'ﳨ' => 'سه', + 'ﳩ' => 'شم', + 'ﳪ' => 'شه', + 'ﳫ' => 'كل', + 'ﳬ' => 'كم', + 'ﳭ' => 'لم', + 'ﳮ' => 'نم', + 'ﳯ' => 'نه', + 'ﳰ' => 'يم', + 'ﳱ' => 'يه', + 'ﳲ' => 'ـَّ', + 'ﳳ' => 'ـُّ', + 'ﳴ' => 'ـِّ', + 'ﳵ' => 'طى', + 'ﳶ' => 'طي', + 'ﳷ' => 'عى', + 'ﳸ' => 'عي', + 'ﳹ' => 'غى', + 'ﳺ' => 'غي', + 'ﳻ' => 'سى', + 'ﳼ' => 'سي', + 'ﳽ' => 'شى', + 'ﳾ' => 'شي', + 'ﳿ' => 'حى', + 'ﴀ' => 'حي', + 'ﴁ' => 'جى', + 'ﴂ' => 'جي', + 'ﴃ' => 'خى', + 'ﴄ' => 'خي', + 'ﴅ' => 'صى', + 'ﴆ' => 'صي', + 'ﴇ' => 'ضى', + 'ﴈ' => 'ضي', + 'ﴉ' => 'شج', + 'ﴊ' => 'شح', + 'ﴋ' => 'شخ', + 'ﴌ' => 'شم', + 'ﴍ' => 'شر', + 'ﴎ' => 'سر', + 'ﴏ' => 'صر', + 'ﴐ' => 'ضر', + 'ﴑ' => 'طى', + 'ﴒ' => 'طي', + 'ﴓ' => 'عى', + 'ﴔ' => 'عي', + 'ﴕ' => 'غى', + 'ﴖ' => 'غي', + 'ﴗ' => 'سى', + 'ﴘ' => 'سي', + 'ﴙ' => 'شى', + 'ﴚ' => 'شي', + 'ﴛ' => 'حى', + 'ﴜ' => 'حي', + 'ﴝ' => 'جى', + 'ﴞ' => 'جي', + 'ﴟ' => 'خى', + 'ﴠ' => 'خي', + 'ﴡ' => 'صى', + 'ﴢ' => 'صي', + 'ﴣ' => 'ضى', + 'ﴤ' => 'ضي', + 'ﴥ' => 'شج', + 'ﴦ' => 'شح', + 'ﴧ' => 'شخ', + 'ﴨ' => 'شم', + 'ﴩ' => 'شر', + 'ﴪ' => 'سر', + 'ﴫ' => 'صر', + 'ﴬ' => 'ضر', + 'ﴭ' => 'شج', + 'ﴮ' => 'شح', + 'ﴯ' => 'شخ', + 'ﴰ' => 'شم', + 'ﴱ' => 'سه', + 'ﴲ' => 'شه', + 'ﴳ' => 'طم', + 'ﴴ' => 'سج', + 'ﴵ' => 'سح', + 'ﴶ' => 'سخ', + 'ﴷ' => 'شج', + 'ﴸ' => 'شح', + 'ﴹ' => 'شخ', + 'ﴺ' => 'طم', + 'ﴻ' => 'ظم', + 'ﴼ' => 'اً', + 'ﴽ' => 'اً', + 'ﵐ' => 'تجم', + 'ﵑ' => 'تحج', + 'ﵒ' => 'تحج', + 'ﵓ' => 'تحم', + 'ﵔ' => 'تخم', + 'ﵕ' => 'تمج', + 'ﵖ' => 'تمح', + 'ﵗ' => 'تمخ', + 'ﵘ' => 'جمح', + 'ﵙ' => 'جمح', + 'ﵚ' => 'حمي', + 'ﵛ' => 'حمى', + 'ﵜ' => 'سحج', + 'ﵝ' => 'سجح', + 'ﵞ' => 'سجى', + 'ﵟ' => 'سمح', + 'ﵠ' => 'سمح', + 'ﵡ' => 'سمج', + 'ﵢ' => 'سمم', + 'ﵣ' => 'سمم', + 'ﵤ' => 'صحح', + 'ﵥ' => 'صحح', + 'ﵦ' => 'صمم', + 'ﵧ' => 'شحم', + 'ﵨ' => 'شحم', + 'ﵩ' => 'شجي', + 'ﵪ' => 'شمخ', + 'ﵫ' => 'شمخ', + 'ﵬ' => 'شمم', + 'ﵭ' => 'شمم', + 'ﵮ' => 'ضحى', + 'ﵯ' => 'ضخم', + 'ﵰ' => 'ضخم', + 'ﵱ' => 'طمح', + 'ﵲ' => 'طمح', + 'ﵳ' => 'طمم', + 'ﵴ' => 'طمي', + 'ﵵ' => 'عجم', + 'ﵶ' => 'عمم', + 'ﵷ' => 'عمم', + 'ﵸ' => 'عمى', + 'ﵹ' => 'غمم', + 'ﵺ' => 'غمي', + 'ﵻ' => 'غمى', + 'ﵼ' => 'فخم', + 'ﵽ' => 'فخم', + 'ﵾ' => 'قمح', + 'ﵿ' => 'قمم', + 'ﶀ' => 'لحم', + 'ﶁ' => 'لحي', + 'ﶂ' => 'لحى', + 'ﶃ' => 'لجج', + 'ﶄ' => 'لجج', + 'ﶅ' => 'لخم', + 'ﶆ' => 'لخم', + 'ﶇ' => 'لمح', + 'ﶈ' => 'لمح', + 'ﶉ' => 'محج', + 'ﶊ' => 'محم', + 'ﶋ' => 'محي', + 'ﶌ' => 'مجح', + 'ﶍ' => 'مجم', + 'ﶎ' => 'مخج', + 'ﶏ' => 'مخم', + 'ﶒ' => 'مجخ', + 'ﶓ' => 'همج', + 'ﶔ' => 'همم', + 'ﶕ' => 'نحم', + 'ﶖ' => 'نحى', + 'ﶗ' => 'نجم', + 'ﶘ' => 'نجم', + 'ﶙ' => 'نجى', + 'ﶚ' => 'نمي', + 'ﶛ' => 'نمى', + 'ﶜ' => 'يمم', + 'ﶝ' => 'يمم', + 'ﶞ' => 'بخي', + 'ﶟ' => 'تجي', + 'ﶠ' => 'تجى', + 'ﶡ' => 'تخي', + 'ﶢ' => 'تخى', + 'ﶣ' => 'تمي', + 'ﶤ' => 'تمى', + 'ﶥ' => 'جمي', + 'ﶦ' => 'جحى', + 'ﶧ' => 'جمى', + 'ﶨ' => 'سخى', + 'ﶩ' => 'صحي', + 'ﶪ' => 'شحي', + 'ﶫ' => 'ضحي', + 'ﶬ' => 'لجي', + 'ﶭ' => 'لمي', + 'ﶮ' => 'يحي', + 'ﶯ' => 'يجي', + 'ﶰ' => 'يمي', + 'ﶱ' => 'ممي', + 'ﶲ' => 'قمي', + 'ﶳ' => 'نحي', + 'ﶴ' => 'قمح', + 'ﶵ' => 'لحم', + 'ﶶ' => 'عمي', + 'ﶷ' => 'كمي', + 'ﶸ' => 'نجح', + 'ﶹ' => 'مخي', + 'ﶺ' => 'لجم', + 'ﶻ' => 'كمم', + 'ﶼ' => 'لجم', + 'ﶽ' => 'نجح', + 'ﶾ' => 'جحي', + 'ﶿ' => 'حجي', + 'ﷀ' => 'مجي', + 'ﷁ' => 'فمي', + 'ﷂ' => 'بحي', + 'ﷃ' => 'كمم', + 'ﷄ' => 'عجم', + 'ﷅ' => 'صمم', + 'ﷆ' => 'سخي', + 'ﷇ' => 'نجي', + 'ﷰ' => 'صلے', + 'ﷱ' => 'قلے', + 'ﷲ' => 'الله', + 'ﷳ' => 'اكبر', + 'ﷴ' => 'محمد', + 'ﷵ' => 'صلعم', + 'ﷶ' => 'رسول', + 'ﷷ' => 'عليه', + 'ﷸ' => 'وسلم', + 'ﷹ' => 'صلى', + 'ﷺ' => 'صلى الله عليه وسلم', + 'ﷻ' => 'جل جلاله', + '﷼' => 'ریال', + '︐' => ',', + '︑' => '、', + '︒' => '。', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︗' => '〖', + '︘' => '〗', + '︙' => '...', + '︰' => '..', + '︱' => '—', + '︲' => '–', + '︳' => '_', + '︴' => '_', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '〔', + '︺' => '〕', + '︻' => '【', + '︼' => '】', + '︽' => '《', + '︾' => '》', + '︿' => '〈', + '﹀' => '〉', + '﹁' => '「', + '﹂' => '」', + '﹃' => '『', + '﹄' => '』', + '﹇' => '[', + '﹈' => ']', + '﹉' => ' ̅', + '﹊' => ' ̅', + '﹋' => ' ̅', + '﹌' => ' ̅', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => '、', + '﹒' => '.', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹘' => '—', + '﹙' => '(', + '﹚' => ')', + '﹛' => '{', + '﹜' => '}', + '﹝' => '〔', + '﹞' => '〕', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + 'ﹰ' => ' ً', + 'ﹱ' => 'ـً', + 'ﹲ' => ' ٌ', + 'ﹴ' => ' ٍ', + 'ﹶ' => ' َ', + 'ﹷ' => 'ـَ', + 'ﹸ' => ' ُ', + 'ﹹ' => 'ـُ', + 'ﹺ' => ' ِ', + 'ﹻ' => 'ـِ', + 'ﹼ' => ' ّ', + 'ﹽ' => 'ـّ', + 'ﹾ' => ' ْ', + 'ﹿ' => 'ـْ', + 'ﺀ' => 'ء', + 'ﺁ' => 'آ', + 'ﺂ' => 'آ', + 'ﺃ' => 'أ', + 'ﺄ' => 'أ', + 'ﺅ' => 'ؤ', + 'ﺆ' => 'ؤ', + 'ﺇ' => 'إ', + 'ﺈ' => 'إ', + 'ﺉ' => 'ئ', + 'ﺊ' => 'ئ', + 'ﺋ' => 'ئ', + 'ﺌ' => 'ئ', + 'ﺍ' => 'ا', + 'ﺎ' => 'ا', + 'ﺏ' => 'ب', + 'ﺐ' => 'ب', + 'ﺑ' => 'ب', + 'ﺒ' => 'ب', + 'ﺓ' => 'ة', + 'ﺔ' => 'ة', + 'ﺕ' => 'ت', + 'ﺖ' => 'ت', + 'ﺗ' => 'ت', + 'ﺘ' => 'ت', + 'ﺙ' => 'ث', + 'ﺚ' => 'ث', + 'ﺛ' => 'ث', + 'ﺜ' => 'ث', + 'ﺝ' => 'ج', + 'ﺞ' => 'ج', + 'ﺟ' => 'ج', + 'ﺠ' => 'ج', + 'ﺡ' => 'ح', + 'ﺢ' => 'ح', + 'ﺣ' => 'ح', + 'ﺤ' => 'ح', + 'ﺥ' => 'خ', + 'ﺦ' => 'خ', + 'ﺧ' => 'خ', + 'ﺨ' => 'خ', + 'ﺩ' => 'د', + 'ﺪ' => 'د', + 'ﺫ' => 'ذ', + 'ﺬ' => 'ذ', + 'ﺭ' => 'ر', + 'ﺮ' => 'ر', + 'ﺯ' => 'ز', + 'ﺰ' => 'ز', + 'ﺱ' => 'س', + 'ﺲ' => 'س', + 'ﺳ' => 'س', + 'ﺴ' => 'س', + 'ﺵ' => 'ش', + 'ﺶ' => 'ش', + 'ﺷ' => 'ش', + 'ﺸ' => 'ش', + 'ﺹ' => 'ص', + 'ﺺ' => 'ص', + 'ﺻ' => 'ص', + 'ﺼ' => 'ص', + 'ﺽ' => 'ض', + 'ﺾ' => 'ض', + 'ﺿ' => 'ض', + 'ﻀ' => 'ض', + 'ﻁ' => 'ط', + 'ﻂ' => 'ط', + 'ﻃ' => 'ط', + 'ﻄ' => 'ط', + 'ﻅ' => 'ظ', + 'ﻆ' => 'ظ', + 'ﻇ' => 'ظ', + 'ﻈ' => 'ظ', + 'ﻉ' => 'ع', + 'ﻊ' => 'ع', + 'ﻋ' => 'ع', + 'ﻌ' => 'ع', + 'ﻍ' => 'غ', + 'ﻎ' => 'غ', + 'ﻏ' => 'غ', + 'ﻐ' => 'غ', + 'ﻑ' => 'ف', + 'ﻒ' => 'ف', + 'ﻓ' => 'ف', + 'ﻔ' => 'ف', + 'ﻕ' => 'ق', + 'ﻖ' => 'ق', + 'ﻗ' => 'ق', + 'ﻘ' => 'ق', + 'ﻙ' => 'ك', + 'ﻚ' => 'ك', + 'ﻛ' => 'ك', + 'ﻜ' => 'ك', + 'ﻝ' => 'ل', + 'ﻞ' => 'ل', + 'ﻟ' => 'ل', + 'ﻠ' => 'ل', + 'ﻡ' => 'م', + 'ﻢ' => 'م', + 'ﻣ' => 'م', + 'ﻤ' => 'م', + 'ﻥ' => 'ن', + 'ﻦ' => 'ن', + 'ﻧ' => 'ن', + 'ﻨ' => 'ن', + 'ﻩ' => 'ه', + 'ﻪ' => 'ه', + 'ﻫ' => 'ه', + 'ﻬ' => 'ه', + 'ﻭ' => 'و', + 'ﻮ' => 'و', + 'ﻯ' => 'ى', + 'ﻰ' => 'ى', + 'ﻱ' => 'ي', + 'ﻲ' => 'ي', + 'ﻳ' => 'ي', + 'ﻴ' => 'ي', + 'ﻵ' => 'لآ', + 'ﻶ' => 'لآ', + 'ﻷ' => 'لأ', + 'ﻸ' => 'لأ', + 'ﻹ' => 'لإ', + 'ﻺ' => 'لإ', + 'ﻻ' => 'لا', + 'ﻼ' => 'لا', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + '0' => '0', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + 'A' => 'A', + 'B' => 'B', + 'C' => 'C', + 'D' => 'D', + 'E' => 'E', + 'F' => 'F', + 'G' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'M' => 'M', + 'N' => 'N', + 'O' => 'O', + 'P' => 'P', + 'Q' => 'Q', + 'R' => 'R', + 'S' => 'S', + 'T' => 'T', + 'U' => 'U', + 'V' => 'V', + 'W' => 'W', + 'X' => 'X', + 'Y' => 'Y', + 'Z' => 'Z', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'e' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'm' => 'm', + 'n' => 'n', + 'o' => 'o', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'v' => 'v', + 'w' => 'w', + 'x' => 'x', + 'y' => 'y', + 'z' => 'z', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '⦅', + '⦆' => '⦆', + '。' => '。', + '「' => '「', + '」' => '」', + '、' => '、', + '・' => '・', + 'ヲ' => 'ヲ', + 'ァ' => 'ァ', + 'ィ' => 'ィ', + 'ゥ' => 'ゥ', + 'ェ' => 'ェ', + 'ォ' => 'ォ', + 'ャ' => 'ャ', + 'ュ' => 'ュ', + 'ョ' => 'ョ', + 'ッ' => 'ッ', + 'ー' => 'ー', + 'ア' => 'ア', + 'イ' => 'イ', + 'ウ' => 'ウ', + 'エ' => 'エ', + 'オ' => 'オ', + 'カ' => 'カ', + 'キ' => 'キ', + 'ク' => 'ク', + 'ケ' => 'ケ', + 'コ' => 'コ', + 'サ' => 'サ', + 'シ' => 'シ', + 'ス' => 'ス', + 'セ' => 'セ', + 'ソ' => 'ソ', + 'タ' => 'タ', + 'チ' => 'チ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ナ' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ネ', + 'ノ' => 'ノ', + 'ハ' => 'ハ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ヘ' => 'ヘ', + 'ホ' => 'ホ', + 'マ' => 'マ', + 'ミ' => 'ミ', + 'ム' => 'ム', + 'メ' => 'メ', + 'モ' => 'モ', + 'ヤ' => 'ヤ', + 'ユ' => 'ユ', + 'ヨ' => 'ヨ', + 'ラ' => 'ラ', + 'リ' => 'リ', + 'ル' => 'ル', + 'レ' => 'レ', + 'ロ' => 'ロ', + 'ワ' => 'ワ', + 'ン' => 'ン', + '゙' => '゙', + '゚' => '゚', + 'ᅠ' => 'ᅠ', + 'ᄀ' => 'ᄀ', + 'ᄁ' => 'ᄁ', + 'ᆪ' => 'ᆪ', + 'ᄂ' => 'ᄂ', + 'ᆬ' => 'ᆬ', + 'ᆭ' => 'ᆭ', + 'ᄃ' => 'ᄃ', + 'ᄄ' => 'ᄄ', + 'ᄅ' => 'ᄅ', + 'ᆰ' => 'ᆰ', + 'ᆱ' => 'ᆱ', + 'ᆲ' => 'ᆲ', + 'ᆳ' => 'ᆳ', + 'ᆴ' => 'ᆴ', + 'ᆵ' => 'ᆵ', + 'ᄚ' => 'ᄚ', + 'ᄆ' => 'ᄆ', + 'ᄇ' => 'ᄇ', + 'ᄈ' => 'ᄈ', + 'ᄡ' => 'ᄡ', + 'ᄉ' => 'ᄉ', + 'ᄊ' => 'ᄊ', + 'ᄋ' => 'ᄋ', + 'ᄌ' => 'ᄌ', + 'ᄍ' => 'ᄍ', + 'ᄎ' => 'ᄎ', + 'ᄏ' => 'ᄏ', + 'ᄐ' => 'ᄐ', + 'ᄑ' => 'ᄑ', + 'ᄒ' => 'ᄒ', + 'ᅡ' => 'ᅡ', + 'ᅢ' => 'ᅢ', + 'ᅣ' => 'ᅣ', + 'ᅤ' => 'ᅤ', + 'ᅥ' => 'ᅥ', + 'ᅦ' => 'ᅦ', + 'ᅧ' => 'ᅧ', + 'ᅨ' => 'ᅨ', + 'ᅩ' => 'ᅩ', + 'ᅪ' => 'ᅪ', + 'ᅫ' => 'ᅫ', + 'ᅬ' => 'ᅬ', + 'ᅭ' => 'ᅭ', + 'ᅮ' => 'ᅮ', + 'ᅯ' => 'ᅯ', + 'ᅰ' => 'ᅰ', + 'ᅱ' => 'ᅱ', + 'ᅲ' => 'ᅲ', + 'ᅳ' => 'ᅳ', + 'ᅴ' => 'ᅴ', + 'ᅵ' => 'ᅵ', + '¢' => '¢', + '£' => '£', + '¬' => '¬', + ' ̄' => ' ̄', + '¦' => '¦', + '¥' => '¥', + '₩' => '₩', + '│' => '│', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '■' => '■', + '○' => '○', + '𝐀' => 'A', + '𝐁' => 'B', + '𝐂' => 'C', + '𝐃' => 'D', + '𝐄' => 'E', + '𝐅' => 'F', + '𝐆' => 'G', + '𝐇' => 'H', + '𝐈' => 'I', + '𝐉' => 'J', + '𝐊' => 'K', + '𝐋' => 'L', + '𝐌' => 'M', + '𝐍' => 'N', + '𝐎' => 'O', + '𝐏' => 'P', + '𝐐' => 'Q', + '𝐑' => 'R', + '𝐒' => 'S', + '𝐓' => 'T', + '𝐔' => 'U', + '𝐕' => 'V', + '𝐖' => 'W', + '𝐗' => 'X', + '𝐘' => 'Y', + '𝐙' => 'Z', + '𝐚' => 'a', + '𝐛' => 'b', + '𝐜' => 'c', + '𝐝' => 'd', + '𝐞' => 'e', + '𝐟' => 'f', + '𝐠' => 'g', + '𝐡' => 'h', + '𝐢' => 'i', + '𝐣' => 'j', + '𝐤' => 'k', + '𝐥' => 'l', + '𝐦' => 'm', + '𝐧' => 'n', + '𝐨' => 'o', + '𝐩' => 'p', + '𝐪' => 'q', + '𝐫' => 'r', + '𝐬' => 's', + '𝐭' => 't', + '𝐮' => 'u', + '𝐯' => 'v', + '𝐰' => 'w', + '𝐱' => 'x', + '𝐲' => 'y', + '𝐳' => 'z', + '𝐴' => 'A', + '𝐵' => 'B', + '𝐶' => 'C', + '𝐷' => 'D', + '𝐸' => 'E', + '𝐹' => 'F', + '𝐺' => 'G', + '𝐻' => 'H', + '𝐼' => 'I', + '𝐽' => 'J', + '𝐾' => 'K', + '𝐿' => 'L', + '𝑀' => 'M', + '𝑁' => 'N', + '𝑂' => 'O', + '𝑃' => 'P', + '𝑄' => 'Q', + '𝑅' => 'R', + '𝑆' => 'S', + '𝑇' => 'T', + '𝑈' => 'U', + '𝑉' => 'V', + '𝑊' => 'W', + '𝑋' => 'X', + '𝑌' => 'Y', + '𝑍' => 'Z', + '𝑎' => 'a', + '𝑏' => 'b', + '𝑐' => 'c', + '𝑑' => 'd', + '𝑒' => 'e', + '𝑓' => 'f', + '𝑔' => 'g', + '𝑖' => 'i', + '𝑗' => 'j', + '𝑘' => 'k', + '𝑙' => 'l', + '𝑚' => 'm', + '𝑛' => 'n', + '𝑜' => 'o', + '𝑝' => 'p', + '𝑞' => 'q', + '𝑟' => 'r', + '𝑠' => 's', + '𝑡' => 't', + '𝑢' => 'u', + '𝑣' => 'v', + '𝑤' => 'w', + '𝑥' => 'x', + '𝑦' => 'y', + '𝑧' => 'z', + '𝑨' => 'A', + '𝑩' => 'B', + '𝑪' => 'C', + '𝑫' => 'D', + '𝑬' => 'E', + '𝑭' => 'F', + '𝑮' => 'G', + '𝑯' => 'H', + '𝑰' => 'I', + '𝑱' => 'J', + '𝑲' => 'K', + '𝑳' => 'L', + '𝑴' => 'M', + '𝑵' => 'N', + '𝑶' => 'O', + '𝑷' => 'P', + '𝑸' => 'Q', + '𝑹' => 'R', + '𝑺' => 'S', + '𝑻' => 'T', + '𝑼' => 'U', + '𝑽' => 'V', + '𝑾' => 'W', + '𝑿' => 'X', + '𝒀' => 'Y', + '𝒁' => 'Z', + '𝒂' => 'a', + '𝒃' => 'b', + '𝒄' => 'c', + '𝒅' => 'd', + '𝒆' => 'e', + '𝒇' => 'f', + '𝒈' => 'g', + '𝒉' => 'h', + '𝒊' => 'i', + '𝒋' => 'j', + '𝒌' => 'k', + '𝒍' => 'l', + '𝒎' => 'm', + '𝒏' => 'n', + '𝒐' => 'o', + '𝒑' => 'p', + '𝒒' => 'q', + '𝒓' => 'r', + '𝒔' => 's', + '𝒕' => 't', + '𝒖' => 'u', + '𝒗' => 'v', + '𝒘' => 'w', + '𝒙' => 'x', + '𝒚' => 'y', + '𝒛' => 'z', + '𝒜' => 'A', + '𝒞' => 'C', + '𝒟' => 'D', + '𝒢' => 'G', + '𝒥' => 'J', + '𝒦' => 'K', + '𝒩' => 'N', + '𝒪' => 'O', + '𝒫' => 'P', + '𝒬' => 'Q', + '𝒮' => 'S', + '𝒯' => 'T', + '𝒰' => 'U', + '𝒱' => 'V', + '𝒲' => 'W', + '𝒳' => 'X', + '𝒴' => 'Y', + '𝒵' => 'Z', + '𝒶' => 'a', + '𝒷' => 'b', + '𝒸' => 'c', + '𝒹' => 'd', + '𝒻' => 'f', + '𝒽' => 'h', + '𝒾' => 'i', + '𝒿' => 'j', + '𝓀' => 'k', + '𝓁' => 'l', + '𝓂' => 'm', + '𝓃' => 'n', + '𝓅' => 'p', + '𝓆' => 'q', + '𝓇' => 'r', + '𝓈' => 's', + '𝓉' => 't', + '𝓊' => 'u', + '𝓋' => 'v', + '𝓌' => 'w', + '𝓍' => 'x', + '𝓎' => 'y', + '𝓏' => 'z', + '𝓐' => 'A', + '𝓑' => 'B', + '𝓒' => 'C', + '𝓓' => 'D', + '𝓔' => 'E', + '𝓕' => 'F', + '𝓖' => 'G', + '𝓗' => 'H', + '𝓘' => 'I', + '𝓙' => 'J', + '𝓚' => 'K', + '𝓛' => 'L', + '𝓜' => 'M', + '𝓝' => 'N', + '𝓞' => 'O', + '𝓟' => 'P', + '𝓠' => 'Q', + '𝓡' => 'R', + '𝓢' => 'S', + '𝓣' => 'T', + '𝓤' => 'U', + '𝓥' => 'V', + '𝓦' => 'W', + '𝓧' => 'X', + '𝓨' => 'Y', + '𝓩' => 'Z', + '𝓪' => 'a', + '𝓫' => 'b', + '𝓬' => 'c', + '𝓭' => 'd', + '𝓮' => 'e', + '𝓯' => 'f', + '𝓰' => 'g', + '𝓱' => 'h', + '𝓲' => 'i', + '𝓳' => 'j', + '𝓴' => 'k', + '𝓵' => 'l', + '𝓶' => 'm', + '𝓷' => 'n', + '𝓸' => 'o', + '𝓹' => 'p', + '𝓺' => 'q', + '𝓻' => 'r', + '𝓼' => 's', + '𝓽' => 't', + '𝓾' => 'u', + '𝓿' => 'v', + '𝔀' => 'w', + '𝔁' => 'x', + '𝔂' => 'y', + '𝔃' => 'z', + '𝔄' => 'A', + '𝔅' => 'B', + '𝔇' => 'D', + '𝔈' => 'E', + '𝔉' => 'F', + '𝔊' => 'G', + '𝔍' => 'J', + '𝔎' => 'K', + '𝔏' => 'L', + '𝔐' => 'M', + '𝔑' => 'N', + '𝔒' => 'O', + '𝔓' => 'P', + '𝔔' => 'Q', + '𝔖' => 'S', + '𝔗' => 'T', + '𝔘' => 'U', + '𝔙' => 'V', + '𝔚' => 'W', + '𝔛' => 'X', + '𝔜' => 'Y', + '𝔞' => 'a', + '𝔟' => 'b', + '𝔠' => 'c', + '𝔡' => 'd', + '𝔢' => 'e', + '𝔣' => 'f', + '𝔤' => 'g', + '𝔥' => 'h', + '𝔦' => 'i', + '𝔧' => 'j', + '𝔨' => 'k', + '𝔩' => 'l', + '𝔪' => 'm', + '𝔫' => 'n', + '𝔬' => 'o', + '𝔭' => 'p', + '𝔮' => 'q', + '𝔯' => 'r', + '𝔰' => 's', + '𝔱' => 't', + '𝔲' => 'u', + '𝔳' => 'v', + '𝔴' => 'w', + '𝔵' => 'x', + '𝔶' => 'y', + '𝔷' => 'z', + '𝔸' => 'A', + '𝔹' => 'B', + '𝔻' => 'D', + '𝔼' => 'E', + '𝔽' => 'F', + '𝔾' => 'G', + '𝕀' => 'I', + '𝕁' => 'J', + '𝕂' => 'K', + '𝕃' => 'L', + '𝕄' => 'M', + '𝕆' => 'O', + '𝕊' => 'S', + '𝕋' => 'T', + '𝕌' => 'U', + '𝕍' => 'V', + '𝕎' => 'W', + '𝕏' => 'X', + '𝕐' => 'Y', + '𝕒' => 'a', + '𝕓' => 'b', + '𝕔' => 'c', + '𝕕' => 'd', + '𝕖' => 'e', + '𝕗' => 'f', + '𝕘' => 'g', + '𝕙' => 'h', + '𝕚' => 'i', + '𝕛' => 'j', + '𝕜' => 'k', + '𝕝' => 'l', + '𝕞' => 'm', + '𝕟' => 'n', + '𝕠' => 'o', + '𝕡' => 'p', + '𝕢' => 'q', + '𝕣' => 'r', + '𝕤' => 's', + '𝕥' => 't', + '𝕦' => 'u', + '𝕧' => 'v', + '𝕨' => 'w', + '𝕩' => 'x', + '𝕪' => 'y', + '𝕫' => 'z', + '𝕬' => 'A', + '𝕭' => 'B', + '𝕮' => 'C', + '𝕯' => 'D', + '𝕰' => 'E', + '𝕱' => 'F', + '𝕲' => 'G', + '𝕳' => 'H', + '𝕴' => 'I', + '𝕵' => 'J', + '𝕶' => 'K', + '𝕷' => 'L', + '𝕸' => 'M', + '𝕹' => 'N', + '𝕺' => 'O', + '𝕻' => 'P', + '𝕼' => 'Q', + '𝕽' => 'R', + '𝕾' => 'S', + '𝕿' => 'T', + '𝖀' => 'U', + '𝖁' => 'V', + '𝖂' => 'W', + '𝖃' => 'X', + '𝖄' => 'Y', + '𝖅' => 'Z', + '𝖆' => 'a', + '𝖇' => 'b', + '𝖈' => 'c', + '𝖉' => 'd', + '𝖊' => 'e', + '𝖋' => 'f', + '𝖌' => 'g', + '𝖍' => 'h', + '𝖎' => 'i', + '𝖏' => 'j', + '𝖐' => 'k', + '𝖑' => 'l', + '𝖒' => 'm', + '𝖓' => 'n', + '𝖔' => 'o', + '𝖕' => 'p', + '𝖖' => 'q', + '𝖗' => 'r', + '𝖘' => 's', + '𝖙' => 't', + '𝖚' => 'u', + '𝖛' => 'v', + '𝖜' => 'w', + '𝖝' => 'x', + '𝖞' => 'y', + '𝖟' => 'z', + '𝖠' => 'A', + '𝖡' => 'B', + '𝖢' => 'C', + '𝖣' => 'D', + '𝖤' => 'E', + '𝖥' => 'F', + '𝖦' => 'G', + '𝖧' => 'H', + '𝖨' => 'I', + '𝖩' => 'J', + '𝖪' => 'K', + '𝖫' => 'L', + '𝖬' => 'M', + '𝖭' => 'N', + '𝖮' => 'O', + '𝖯' => 'P', + '𝖰' => 'Q', + '𝖱' => 'R', + '𝖲' => 'S', + '𝖳' => 'T', + '𝖴' => 'U', + '𝖵' => 'V', + '𝖶' => 'W', + '𝖷' => 'X', + '𝖸' => 'Y', + '𝖹' => 'Z', + '𝖺' => 'a', + '𝖻' => 'b', + '𝖼' => 'c', + '𝖽' => 'd', + '𝖾' => 'e', + '𝖿' => 'f', + '𝗀' => 'g', + '𝗁' => 'h', + '𝗂' => 'i', + '𝗃' => 'j', + '𝗄' => 'k', + '𝗅' => 'l', + '𝗆' => 'm', + '𝗇' => 'n', + '𝗈' => 'o', + '𝗉' => 'p', + '𝗊' => 'q', + '𝗋' => 'r', + '𝗌' => 's', + '𝗍' => 't', + '𝗎' => 'u', + '𝗏' => 'v', + '𝗐' => 'w', + '𝗑' => 'x', + '𝗒' => 'y', + '𝗓' => 'z', + '𝗔' => 'A', + '𝗕' => 'B', + '𝗖' => 'C', + '𝗗' => 'D', + '𝗘' => 'E', + '𝗙' => 'F', + '𝗚' => 'G', + '𝗛' => 'H', + '𝗜' => 'I', + '𝗝' => 'J', + '𝗞' => 'K', + '𝗟' => 'L', + '𝗠' => 'M', + '𝗡' => 'N', + '𝗢' => 'O', + '𝗣' => 'P', + '𝗤' => 'Q', + '𝗥' => 'R', + '𝗦' => 'S', + '𝗧' => 'T', + '𝗨' => 'U', + '𝗩' => 'V', + '𝗪' => 'W', + '𝗫' => 'X', + '𝗬' => 'Y', + '𝗭' => 'Z', + '𝗮' => 'a', + '𝗯' => 'b', + '𝗰' => 'c', + '𝗱' => 'd', + '𝗲' => 'e', + '𝗳' => 'f', + '𝗴' => 'g', + '𝗵' => 'h', + '𝗶' => 'i', + '𝗷' => 'j', + '𝗸' => 'k', + '𝗹' => 'l', + '𝗺' => 'm', + '𝗻' => 'n', + '𝗼' => 'o', + '𝗽' => 'p', + '𝗾' => 'q', + '𝗿' => 'r', + '𝘀' => 's', + '𝘁' => 't', + '𝘂' => 'u', + '𝘃' => 'v', + '𝘄' => 'w', + '𝘅' => 'x', + '𝘆' => 'y', + '𝘇' => 'z', + '𝘈' => 'A', + '𝘉' => 'B', + '𝘊' => 'C', + '𝘋' => 'D', + '𝘌' => 'E', + '𝘍' => 'F', + '𝘎' => 'G', + '𝘏' => 'H', + '𝘐' => 'I', + '𝘑' => 'J', + '𝘒' => 'K', + '𝘓' => 'L', + '𝘔' => 'M', + '𝘕' => 'N', + '𝘖' => 'O', + '𝘗' => 'P', + '𝘘' => 'Q', + '𝘙' => 'R', + '𝘚' => 'S', + '𝘛' => 'T', + '𝘜' => 'U', + '𝘝' => 'V', + '𝘞' => 'W', + '𝘟' => 'X', + '𝘠' => 'Y', + '𝘡' => 'Z', + '𝘢' => 'a', + '𝘣' => 'b', + '𝘤' => 'c', + '𝘥' => 'd', + '𝘦' => 'e', + '𝘧' => 'f', + '𝘨' => 'g', + '𝘩' => 'h', + '𝘪' => 'i', + '𝘫' => 'j', + '𝘬' => 'k', + '𝘭' => 'l', + '𝘮' => 'm', + '𝘯' => 'n', + '𝘰' => 'o', + '𝘱' => 'p', + '𝘲' => 'q', + '𝘳' => 'r', + '𝘴' => 's', + '𝘵' => 't', + '𝘶' => 'u', + '𝘷' => 'v', + '𝘸' => 'w', + '𝘹' => 'x', + '𝘺' => 'y', + '𝘻' => 'z', + '𝘼' => 'A', + '𝘽' => 'B', + '𝘾' => 'C', + '𝘿' => 'D', + '𝙀' => 'E', + '𝙁' => 'F', + '𝙂' => 'G', + '𝙃' => 'H', + '𝙄' => 'I', + '𝙅' => 'J', + '𝙆' => 'K', + '𝙇' => 'L', + '𝙈' => 'M', + '𝙉' => 'N', + '𝙊' => 'O', + '𝙋' => 'P', + '𝙌' => 'Q', + '𝙍' => 'R', + '𝙎' => 'S', + '𝙏' => 'T', + '𝙐' => 'U', + '𝙑' => 'V', + '𝙒' => 'W', + '𝙓' => 'X', + '𝙔' => 'Y', + '𝙕' => 'Z', + '𝙖' => 'a', + '𝙗' => 'b', + '𝙘' => 'c', + '𝙙' => 'd', + '𝙚' => 'e', + '𝙛' => 'f', + '𝙜' => 'g', + '𝙝' => 'h', + '𝙞' => 'i', + '𝙟' => 'j', + '𝙠' => 'k', + '𝙡' => 'l', + '𝙢' => 'm', + '𝙣' => 'n', + '𝙤' => 'o', + '𝙥' => 'p', + '𝙦' => 'q', + '𝙧' => 'r', + '𝙨' => 's', + '𝙩' => 't', + '𝙪' => 'u', + '𝙫' => 'v', + '𝙬' => 'w', + '𝙭' => 'x', + '𝙮' => 'y', + '𝙯' => 'z', + '𝙰' => 'A', + '𝙱' => 'B', + '𝙲' => 'C', + '𝙳' => 'D', + '𝙴' => 'E', + '𝙵' => 'F', + '𝙶' => 'G', + '𝙷' => 'H', + '𝙸' => 'I', + '𝙹' => 'J', + '𝙺' => 'K', + '𝙻' => 'L', + '𝙼' => 'M', + '𝙽' => 'N', + '𝙾' => 'O', + '𝙿' => 'P', + '𝚀' => 'Q', + '𝚁' => 'R', + '𝚂' => 'S', + '𝚃' => 'T', + '𝚄' => 'U', + '𝚅' => 'V', + '𝚆' => 'W', + '𝚇' => 'X', + '𝚈' => 'Y', + '𝚉' => 'Z', + '𝚊' => 'a', + '𝚋' => 'b', + '𝚌' => 'c', + '𝚍' => 'd', + '𝚎' => 'e', + '𝚏' => 'f', + '𝚐' => 'g', + '𝚑' => 'h', + '𝚒' => 'i', + '𝚓' => 'j', + '𝚔' => 'k', + '𝚕' => 'l', + '𝚖' => 'm', + '𝚗' => 'n', + '𝚘' => 'o', + '𝚙' => 'p', + '𝚚' => 'q', + '𝚛' => 'r', + '𝚜' => 's', + '𝚝' => 't', + '𝚞' => 'u', + '𝚟' => 'v', + '𝚠' => 'w', + '𝚡' => 'x', + '𝚢' => 'y', + '𝚣' => 'z', + '𝚤' => 'ı', + '𝚥' => 'ȷ', + '𝚨' => 'Α', + '𝚩' => 'Β', + '𝚪' => 'Γ', + '𝚫' => 'Δ', + '𝚬' => 'Ε', + '𝚭' => 'Ζ', + '𝚮' => 'Η', + '𝚯' => 'Θ', + '𝚰' => 'Ι', + '𝚱' => 'Κ', + '𝚲' => 'Λ', + '𝚳' => 'Μ', + '𝚴' => 'Ν', + '𝚵' => 'Ξ', + '𝚶' => 'Ο', + '𝚷' => 'Π', + '𝚸' => 'Ρ', + '𝚹' => 'Θ', + '𝚺' => 'Σ', + '𝚻' => 'Τ', + '𝚼' => 'Υ', + '𝚽' => 'Φ', + '𝚾' => 'Χ', + '𝚿' => 'Ψ', + '𝛀' => 'Ω', + '𝛁' => '∇', + '𝛂' => 'α', + '𝛃' => 'β', + '𝛄' => 'γ', + '𝛅' => 'δ', + '𝛆' => 'ε', + '𝛇' => 'ζ', + '𝛈' => 'η', + '𝛉' => 'θ', + '𝛊' => 'ι', + '𝛋' => 'κ', + '𝛌' => 'λ', + '𝛍' => 'μ', + '𝛎' => 'ν', + '𝛏' => 'ξ', + '𝛐' => 'ο', + '𝛑' => 'π', + '𝛒' => 'ρ', + '𝛓' => 'ς', + '𝛔' => 'σ', + '𝛕' => 'τ', + '𝛖' => 'υ', + '𝛗' => 'φ', + '𝛘' => 'χ', + '𝛙' => 'ψ', + '𝛚' => 'ω', + '𝛛' => '∂', + '𝛜' => 'ε', + '𝛝' => 'θ', + '𝛞' => 'κ', + '𝛟' => 'φ', + '𝛠' => 'ρ', + '𝛡' => 'π', + '𝛢' => 'Α', + '𝛣' => 'Β', + '𝛤' => 'Γ', + '𝛥' => 'Δ', + '𝛦' => 'Ε', + '𝛧' => 'Ζ', + '𝛨' => 'Η', + '𝛩' => 'Θ', + '𝛪' => 'Ι', + '𝛫' => 'Κ', + '𝛬' => 'Λ', + '𝛭' => 'Μ', + '𝛮' => 'Ν', + '𝛯' => 'Ξ', + '𝛰' => 'Ο', + '𝛱' => 'Π', + '𝛲' => 'Ρ', + '𝛳' => 'Θ', + '𝛴' => 'Σ', + '𝛵' => 'Τ', + '𝛶' => 'Υ', + '𝛷' => 'Φ', + '𝛸' => 'Χ', + '𝛹' => 'Ψ', + '𝛺' => 'Ω', + '𝛻' => '∇', + '𝛼' => 'α', + '𝛽' => 'β', + '𝛾' => 'γ', + '𝛿' => 'δ', + '𝜀' => 'ε', + '𝜁' => 'ζ', + '𝜂' => 'η', + '𝜃' => 'θ', + '𝜄' => 'ι', + '𝜅' => 'κ', + '𝜆' => 'λ', + '𝜇' => 'μ', + '𝜈' => 'ν', + '𝜉' => 'ξ', + '𝜊' => 'ο', + '𝜋' => 'π', + '𝜌' => 'ρ', + '𝜍' => 'ς', + '𝜎' => 'σ', + '𝜏' => 'τ', + '𝜐' => 'υ', + '𝜑' => 'φ', + '𝜒' => 'χ', + '𝜓' => 'ψ', + '𝜔' => 'ω', + '𝜕' => '∂', + '𝜖' => 'ε', + '𝜗' => 'θ', + '𝜘' => 'κ', + '𝜙' => 'φ', + '𝜚' => 'ρ', + '𝜛' => 'π', + '𝜜' => 'Α', + '𝜝' => 'Β', + '𝜞' => 'Γ', + '𝜟' => 'Δ', + '𝜠' => 'Ε', + '𝜡' => 'Ζ', + '𝜢' => 'Η', + '𝜣' => 'Θ', + '𝜤' => 'Ι', + '𝜥' => 'Κ', + '𝜦' => 'Λ', + '𝜧' => 'Μ', + '𝜨' => 'Ν', + '𝜩' => 'Ξ', + '𝜪' => 'Ο', + '𝜫' => 'Π', + '𝜬' => 'Ρ', + '𝜭' => 'Θ', + '𝜮' => 'Σ', + '𝜯' => 'Τ', + '𝜰' => 'Υ', + '𝜱' => 'Φ', + '𝜲' => 'Χ', + '𝜳' => 'Ψ', + '𝜴' => 'Ω', + '𝜵' => '∇', + '𝜶' => 'α', + '𝜷' => 'β', + '𝜸' => 'γ', + '𝜹' => 'δ', + '𝜺' => 'ε', + '𝜻' => 'ζ', + '𝜼' => 'η', + '𝜽' => 'θ', + '𝜾' => 'ι', + '𝜿' => 'κ', + '𝝀' => 'λ', + '𝝁' => 'μ', + '𝝂' => 'ν', + '𝝃' => 'ξ', + '𝝄' => 'ο', + '𝝅' => 'π', + '𝝆' => 'ρ', + '𝝇' => 'ς', + '𝝈' => 'σ', + '𝝉' => 'τ', + '𝝊' => 'υ', + '𝝋' => 'φ', + '𝝌' => 'χ', + '𝝍' => 'ψ', + '𝝎' => 'ω', + '𝝏' => '∂', + '𝝐' => 'ε', + '𝝑' => 'θ', + '𝝒' => 'κ', + '𝝓' => 'φ', + '𝝔' => 'ρ', + '𝝕' => 'π', + '𝝖' => 'Α', + '𝝗' => 'Β', + '𝝘' => 'Γ', + '𝝙' => 'Δ', + '𝝚' => 'Ε', + '𝝛' => 'Ζ', + '𝝜' => 'Η', + '𝝝' => 'Θ', + '𝝞' => 'Ι', + '𝝟' => 'Κ', + '𝝠' => 'Λ', + '𝝡' => 'Μ', + '𝝢' => 'Ν', + '𝝣' => 'Ξ', + '𝝤' => 'Ο', + '𝝥' => 'Π', + '𝝦' => 'Ρ', + '𝝧' => 'Θ', + '𝝨' => 'Σ', + '𝝩' => 'Τ', + '𝝪' => 'Υ', + '𝝫' => 'Φ', + '𝝬' => 'Χ', + '𝝭' => 'Ψ', + '𝝮' => 'Ω', + '𝝯' => '∇', + '𝝰' => 'α', + '𝝱' => 'β', + '𝝲' => 'γ', + '𝝳' => 'δ', + '𝝴' => 'ε', + '𝝵' => 'ζ', + '𝝶' => 'η', + '𝝷' => 'θ', + '𝝸' => 'ι', + '𝝹' => 'κ', + '𝝺' => 'λ', + '𝝻' => 'μ', + '𝝼' => 'ν', + '𝝽' => 'ξ', + '𝝾' => 'ο', + '𝝿' => 'π', + '𝞀' => 'ρ', + '𝞁' => 'ς', + '𝞂' => 'σ', + '𝞃' => 'τ', + '𝞄' => 'υ', + '𝞅' => 'φ', + '𝞆' => 'χ', + '𝞇' => 'ψ', + '𝞈' => 'ω', + '𝞉' => '∂', + '𝞊' => 'ε', + '𝞋' => 'θ', + '𝞌' => 'κ', + '𝞍' => 'φ', + '𝞎' => 'ρ', + '𝞏' => 'π', + '𝞐' => 'Α', + '𝞑' => 'Β', + '𝞒' => 'Γ', + '𝞓' => 'Δ', + '𝞔' => 'Ε', + '𝞕' => 'Ζ', + '𝞖' => 'Η', + '𝞗' => 'Θ', + '𝞘' => 'Ι', + '𝞙' => 'Κ', + '𝞚' => 'Λ', + '𝞛' => 'Μ', + '𝞜' => 'Ν', + '𝞝' => 'Ξ', + '𝞞' => 'Ο', + '𝞟' => 'Π', + '𝞠' => 'Ρ', + '𝞡' => 'Θ', + '𝞢' => 'Σ', + '𝞣' => 'Τ', + '𝞤' => 'Υ', + '𝞥' => 'Φ', + '𝞦' => 'Χ', + '𝞧' => 'Ψ', + '𝞨' => 'Ω', + '𝞩' => '∇', + '𝞪' => 'α', + '𝞫' => 'β', + '𝞬' => 'γ', + '𝞭' => 'δ', + '𝞮' => 'ε', + '𝞯' => 'ζ', + '𝞰' => 'η', + '𝞱' => 'θ', + '𝞲' => 'ι', + '𝞳' => 'κ', + '𝞴' => 'λ', + '𝞵' => 'μ', + '𝞶' => 'ν', + '𝞷' => 'ξ', + '𝞸' => 'ο', + '𝞹' => 'π', + '𝞺' => 'ρ', + '𝞻' => 'ς', + '𝞼' => 'σ', + '𝞽' => 'τ', + '𝞾' => 'υ', + '𝞿' => 'φ', + '𝟀' => 'χ', + '𝟁' => 'ψ', + '𝟂' => 'ω', + '𝟃' => '∂', + '𝟄' => 'ε', + '𝟅' => 'θ', + '𝟆' => 'κ', + '𝟇' => 'φ', + '𝟈' => 'ρ', + '𝟉' => 'π', + '𝟊' => 'Ϝ', + '𝟋' => 'ϝ', + '𝟎' => '0', + '𝟏' => '1', + '𝟐' => '2', + '𝟑' => '3', + '𝟒' => '4', + '𝟓' => '5', + '𝟔' => '6', + '𝟕' => '7', + '𝟖' => '8', + '𝟗' => '9', + '𝟘' => '0', + '𝟙' => '1', + '𝟚' => '2', + '𝟛' => '3', + '𝟜' => '4', + '𝟝' => '5', + '𝟞' => '6', + '𝟟' => '7', + '𝟠' => '8', + '𝟡' => '9', + '𝟢' => '0', + '𝟣' => '1', + '𝟤' => '2', + '𝟥' => '3', + '𝟦' => '4', + '𝟧' => '5', + '𝟨' => '6', + '𝟩' => '7', + '𝟪' => '8', + '𝟫' => '9', + '𝟬' => '0', + '𝟭' => '1', + '𝟮' => '2', + '𝟯' => '3', + '𝟰' => '4', + '𝟱' => '5', + '𝟲' => '6', + '𝟳' => '7', + '𝟴' => '8', + '𝟵' => '9', + '𝟶' => '0', + '𝟷' => '1', + '𝟸' => '2', + '𝟹' => '3', + '𝟺' => '4', + '𝟻' => '5', + '𝟼' => '6', + '𝟽' => '7', + '𝟾' => '8', + '𝟿' => '9', + '𞸀' => 'ا', + '𞸁' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'و', + '𞸆' => 'ز', + '𞸇' => 'ح', + '𞸈' => 'ط', + '𞸉' => 'ي', + '𞸊' => 'ك', + '𞸋' => 'ل', + '𞸌' => 'م', + '𞸍' => 'ن', + '𞸎' => 'س', + '𞸏' => 'ع', + '𞸐' => 'ف', + '𞸑' => 'ص', + '𞸒' => 'ق', + '𞸓' => 'ر', + '𞸔' => 'ش', + '𞸕' => 'ت', + '𞸖' => 'ث', + '𞸗' => 'خ', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'ٮ', + '𞸝' => 'ں', + '𞸞' => 'ڡ', + '𞸟' => 'ٯ', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'ه', + '𞸧' => 'ح', + '𞸩' => 'ي', + '𞸪' => 'ك', + '𞸫' => 'ل', + '𞸬' => 'م', + '𞸭' => 'ن', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'ف', + '𞸱' => 'ص', + '𞸲' => 'ق', + '𞸴' => 'ش', + '𞸵' => 'ت', + '𞸶' => 'ث', + '𞸷' => 'خ', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'ح', + '𞹉' => 'ي', + '𞹋' => 'ل', + '𞹍' => 'ن', + '𞹎' => 'س', + '𞹏' => 'ع', + '𞹑' => 'ص', + '𞹒' => 'ق', + '𞹔' => 'ش', + '𞹗' => 'خ', + '𞹙' => 'ض', + '𞹛' => 'غ', + '𞹝' => 'ں', + '𞹟' => 'ٯ', + '𞹡' => 'ب', + '𞹢' => 'ج', + '𞹤' => 'ه', + '𞹧' => 'ح', + '𞹨' => 'ط', + '𞹩' => 'ي', + '𞹪' => 'ك', + '𞹬' => 'م', + '𞹭' => 'ن', + '𞹮' => 'س', + '𞹯' => 'ع', + '𞹰' => 'ف', + '𞹱' => 'ص', + '𞹲' => 'ق', + '𞹴' => 'ش', + '𞹵' => 'ت', + '𞹶' => 'ث', + '𞹷' => 'خ', + '𞹹' => 'ض', + '𞹺' => 'ظ', + '𞹻' => 'غ', + '𞹼' => 'ٮ', + '𞹾' => 'ڡ', + '𞺀' => 'ا', + '𞺁' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'ه', + '𞺅' => 'و', + '𞺆' => 'ز', + '𞺇' => 'ح', + '𞺈' => 'ط', + '𞺉' => 'ي', + '𞺋' => 'ل', + '𞺌' => 'م', + '𞺍' => 'ن', + '𞺎' => 'س', + '𞺏' => 'ع', + '𞺐' => 'ف', + '𞺑' => 'ص', + '𞺒' => 'ق', + '𞺓' => 'ر', + '𞺔' => 'ش', + '𞺕' => 'ت', + '𞺖' => 'ث', + '𞺗' => 'خ', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'و', + '𞺦' => 'ز', + '𞺧' => 'ح', + '𞺨' => 'ط', + '𞺩' => 'ي', + '𞺫' => 'ل', + '𞺬' => 'م', + '𞺭' => 'ن', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'ف', + '𞺱' => 'ص', + '𞺲' => 'ق', + '𞺳' => 'ر', + '𞺴' => 'ش', + '𞺵' => 'ت', + '𞺶' => 'ث', + '𞺷' => 'خ', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + '🄁' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + '🄐' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + '🄝' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => 'C', + '🄬' => 'R', + '🄭' => 'CD', + '🄮' => 'WZ', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + '🅁' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + '🅍' => 'SS', + '🅎' => 'PPV', + '🅏' => 'WC', + '🅪' => 'MC', + '🅫' => 'MD', + '🅬' => 'MR', + '🆐' => 'DJ', + '🈀' => 'ほか', + '🈁' => 'ココ', + '🈂' => 'サ', + '🈐' => '手', + '🈑' => '字', + '🈒' => '双', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => '解', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => '無', + '🈛' => '料', + '🈜' => '前', + '🈝' => '後', + '🈞' => '再', + '🈟' => '新', + '🈠' => '初', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => '吹', + '🈦' => '演', + '🈧' => '投', + '🈨' => '捕', + '🈩' => '一', + '🈪' => '三', + '🈫' => '遊', + '🈬' => '左', + '🈭' => '中', + '🈮' => '右', + '🈯' => '指', + '🈰' => '走', + '🈱' => '打', + '🈲' => '禁', + '🈳' => '空', + '🈴' => '合', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => '営', + '🈻' => '配', + '🉀' => '〔本〕', + '🉁' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔勝〕', + '🉈' => '〔敗〕', + '🉐' => '得', + '🉑' => '可', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', +); diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 0000000..3608e5c --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php b/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 0000000..c9a29d7 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(string $string, int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(string $string, int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize($string, $form); } +} diff --git a/plugins/email/vendor/symfony/polyfill-intl-normalizer/composer.json b/plugins/email/vendor/symfony/polyfill-intl-normalizer/composer.json new file mode 100644 index 0000000..8f4cfb4 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-intl-normalizer/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/polyfill-intl-normalizer", + "type": "library", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "normalizer"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/LICENSE b/plugins/email/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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/plugins/email/vendor/symfony/polyfill-mbstring/Mbstring.php b/plugins/email/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..8b3b758 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,869 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/README.md b/plugins/email/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..4efb599 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..a22eca5 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', +); diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap.php b/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..c45624c --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap80.php b/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..e86754e --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string $string, string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(string $string): string { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(string $string, string $charset = null, string $transfer_encoding = null, string $newline = "\r\n", int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(string $string, array $map, string $encoding = null): string { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(string $string, array $map, string $encoding = null, bool $hex = false): string { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(string $string, int $mode, string $encoding = null): string { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(string $encoding): array { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(string $string, array|string|null $encodings = null, bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(string $string, &$result = array()): bool { parse_str($string, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(string $string, string $encoding = null): int { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(string $string, string $encoding = null): string { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(string $string, string $encoding = null): string { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(string $string, int $start, int $length = null, string $encoding = null): string { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(string $haystack, string $needle, bool $before_needle = false, string $encoding = null): string|false { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(string $haystack, string $needle, bool $before_needle = false, string $encoding = null): string|false { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(string $haystack, string $needle, bool $before_needle = false, string $encoding = null): string|false { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(string $haystack, string $needle, bool $before_needle = false, string $encoding = null): string|false { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(string $string, string $encoding = null): int { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(string $haystack, string $needle, string $encoding = null): int { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(string $string, int $status): string { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(string $to_encoding, array|string $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(string $string, string $encoding = null): int|false { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(int $codepoint, string $encoding = null): string|false { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(string $string, string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(string $string, int $length = 1, string $encoding = null): array { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/plugins/email/vendor/symfony/polyfill-mbstring/composer.json b/plugins/email/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..ca82638 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/plugins/email/vendor/symfony/polyfill-php72/LICENSE b/plugins/email/vendor/symfony/polyfill-php72/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-php72/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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/plugins/email/vendor/symfony/polyfill-php72/Php72.php b/plugins/email/vendor/symfony/polyfill-php72/Php72.php new file mode 100644 index 0000000..2b706d4 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-php72/Php72.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = [ + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ]; + + return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + // On 32-bit systems, PHP_INT_SIZE is 4, + return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) []; + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null === $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } +} diff --git a/plugins/email/vendor/symfony/polyfill-php72/README.md b/plugins/email/vendor/symfony/polyfill-php72/README.md new file mode 100644 index 0000000..59dec8a --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-php72/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides constants added to PHP 7.2: +- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig) +- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/plugins/email/vendor/symfony/polyfill-php72/bootstrap.php b/plugins/email/vendor/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 0000000..b5c92d4 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (\PHP_VERSION_ID >= 70200) { + return; +} + +if (!defined('PHP_FLOAT_DIG')) { + define('PHP_FLOAT_DIG', 15); +} +if (!defined('PHP_FLOAT_EPSILON')) { + define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); +} +if (!defined('PHP_FLOAT_MIN')) { + define('PHP_FLOAT_MIN', 2.2250738585072E-308); +} +if (!defined('PHP_FLOAT_MAX')) { + define('PHP_FLOAT_MAX', 1.7976931348623157E+308); +} +if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); +} + +if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } +} +if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } +} +if (!function_exists('utf8_encode')) { + function utf8_encode($string) { return p\Php72::utf8_encode($string); } +} +if (!function_exists('utf8_decode')) { + function utf8_decode($string) { return p\Php72::utf8_decode($string); } +} +if (!function_exists('spl_object_id')) { + function spl_object_id($object) { return p\Php72::spl_object_id($object); } +} +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Php72::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} diff --git a/plugins/email/vendor/symfony/polyfill-php72/composer.json b/plugins/email/vendor/symfony/polyfill-php72/composer.json new file mode 100644 index 0000000..7946892 --- /dev/null +++ b/plugins/email/vendor/symfony/polyfill-php72/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/plugins/error/.gitignore b/plugins/error/.gitignore new file mode 100644 index 0000000..5ce2fa3 --- /dev/null +++ b/plugins/error/.gitignore @@ -0,0 +1,9 @@ +# OS Generated +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db +*.swp + +# phpstorm +.idea/* diff --git a/plugins/error/CHANGELOG.md b/plugins/error/CHANGELOG.md new file mode 100644 index 0000000..eec5161 --- /dev/null +++ b/plugins/error/CHANGELOG.md @@ -0,0 +1,91 @@ +# v1.8.0 +## 09/07/2021 + +1. [](#new) + * Require **Grav 1.7.0** + * Added support for `{% throw 404 'Not Found' %}` from twig template to show the error page +1. [](#improved) + * Do not cache 404 error pages by default + +# v1.7.1 +## 10/08/2020 + +1. [](#bugfix) + * Fixed error page being cached, fixes issue with non-existing resources which later become available + +# v1.7.0 +## 07/01/2020 + +1. [](#new) + * Require Grav v1.6 +1. [](#bugfix) + * Added translated title programmatically [#40](https://github.com/getgrav/grav-plugin-error/pull/40) + +# v1.6.2 +## 05/09/2019 + +1. [](#new) + * Fixed a few issues found by phpstan + * Added `ru` and `uk` translations [#36](https://github.com/getgrav/grav-plugin-error/pull/36) + +# v1.6.1 +## 03/09/2018 + +1. [](#improved) + * Added Polish + Catalan translation + * Updated `README.md` to reference custom error pages + +# v1.6.0 +## 10/19/2016 + +1. [](#improved) + * Added Croatian translation + * Improved `autoescape: true` support +1. [](#bugfix) + * Fixed issue where template file for `error` page type is only available if page was not found + +# v1.5.1 +## 07/18/2016 + +1. [](#improved) + * Added chinese and german translations +1. [](#bugfix) + * Fixed issue with the Smartypants plugin running before Twig was processed + +# v1.5.0 +## 07/14/2015 + +1. [](#improved) + * Translate some blueprint configuration options + * Allow translating the error message + * Added french, russian, romanian, danish, italian + +# v1.4.1 +## 12/11/2015 + +1. [](#bugfix) + * Fixed CLI command for PHP 5.5 and lower + +# v1.4.0 +## 11/21/2015 + +1. [](#new) + * Implemented CLI commands for the plugin + +# v1.3.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.2.2 +## 01/06/2015 + +1. [](#new) + * Added a default `error.json.twig` file + +# v1.2.1 +## 11/30/2014 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/error/LICENSE b/plugins/error/LICENSE new file mode 100644 index 0000000..484793a --- /dev/null +++ b/plugins/error/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +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/plugins/error/README.md b/plugins/error/README.md new file mode 100644 index 0000000..ef24726 --- /dev/null +++ b/plugins/error/README.md @@ -0,0 +1,93 @@ +# Grav Error Plugin + +![GPM Installation](assets/readme_1.png) + +`error` is a [Grav](http://github.com/getgrav/grav) Plugin and allows to redirect errors to nice output pages. + +This plugin is included in any package distributed that contains Grav. If you decide to clone Grav from GitHub you will most likely want to install this. + +# Installation + +Installing the Error plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install error + +This will install the Error plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/error`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `error`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-error) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/error + +>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Problems](https://github.com/getgrav/grav-plugin-problems) plugin, and a theme to be installed in order to operate. + +# Usage + +The `error` plugin doesn't require any configuration. The moment you install it, it is ready to use. + +Something you might want to do is to override the look and feel of the error page, and with Grav it is super easy. + +### Template + +Copy the template file [error.html.twig](templates/error.html.twig) into the `templates` folder of your custom theme and that is it. + +``` +/your/site/grav/user/themes/custom-theme/templates/error.html.twig +``` + +You can now edit the override and tweak it however you prefer. + +### Page + +Copy the page file [error.md](pages/error.md) into the `pages` folder of your user directory and that is it. + +``` +/your/site/grav/user/pages/error/error.md +``` + +You can now edit the override and tweak it however you prefer. + +# Custom error pages + +The configuration allows to specify pages different than `/error` for specific error codes. By default, the `404` error leads to the `/error` page. If you change that, make sure the page you point to has a `error` template (which means, its markdown file is `error.md` or in the page frontmatter you specify `template: error`. + +# CLI Usage +The `error` plugin comes with a CLI command that outputs the `grav.log` in a beautified way, with possibility of limiting the amount of errors displayed, as well as include the trace in the output. + +### Commands + +| `bin/plugin error log` | | +|------------------------|-----------------------------------------------------------------| +| [ --limit N \| -l N ] | The amount of errors to display. Default is 5 | +| [ --trace \| -t ] | When used, it will add the backtrace in the output of the error | + + +# Updating + +As development for the Error plugin continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Error is easy, and can be done through Grav's GPM system, as well as manually. + +## GPM Update (Preferred) + +The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: + + bin/gpm update error + +This command will check your Grav install to see if your Error plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. + +## Manual Update + +Manually updating Error is pretty simple. Here is what you will need to do to get this done: + +* Delete the `your/site/user/plugins/error` directory. +* Download the new version of the Error plugin from either [GitHub](https://github.com/getgrav/grav-plugin-error) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras). +* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `error`. +* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`. + +> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. diff --git a/plugins/error/assets/readme_1.png b/plugins/error/assets/readme_1.png new file mode 100644 index 0000000..930b87b Binary files /dev/null and b/plugins/error/assets/readme_1.png differ diff --git a/plugins/error/blueprints.yaml b/plugins/error/blueprints.yaml new file mode 100644 index 0000000..b642158 --- /dev/null +++ b/plugins/error/blueprints.yaml @@ -0,0 +1,36 @@ +name: Error +version: 1.8.0 +description: Displays the error page. +type: plugin +slug: error +icon: warning +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-error +keywords: error, plugin, required +bugs: https://github.com/getgrav/grav-plugin-error/issues +license: MIT +dependencies: + - { name: grav, version: '>=1.7.0' } + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + routes.404: + type: text + size: medium + label: PLUGIN_ERROR.ROUTE_404 + default: '/error' diff --git a/plugins/error/cli/LogCommand.php b/plugins/error/cli/LogCommand.php new file mode 100644 index 0000000..d29354a --- /dev/null +++ b/plugins/error/cli/LogCommand.php @@ -0,0 +1,130 @@ + 'green', + 'INFO' => 'cyan', + 'NOTICE' => 'yellow', + 'WARNING' => 'yellow', + 'ERROR' => 'red', + 'CRITICAL' => 'red', + 'ALERT' => 'red', + 'EMERGENCY' => 'magenta' + ]; + + /** + * + */ + protected function configure() + { + $this->logfile = LOG_DIR . 'grav.log'; + $this + ->setName("log") + ->setDescription("Outputs the Error Log") + ->addOption( + 'trace', + 't', + InputOption::VALUE_NONE, + 'Include the errors stack trace in the output' + ) + ->addOption( + 'limit', + 'l', + InputArgument::OPTIONAL, + 'Outputs only the last X amount of errors. Use as --limit 10 / -l 10 [default 5]', + 5 + ) + ->setHelp('The log outputs the Errors Log in Console') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = [ + 'trace' => $this->input->getOption('trace'), + 'limit' => $this->input->getOption('limit') + ]; + + if (!file_exists($this->logfile)) { + $this->output->writeln("\n" . "Log file not found." . "\n"); + exit; + } + + $log = file_get_contents($this->logfile); + $lines = explode("\n", $log); + + if (!is_numeric($this->options['limit'])) { + $this->options['limit'] = 5; + } + + $lines = array_slice($lines, -($this->options['limit'] + 1)); + + foreach ($lines as $line) { + $this->output->writeln($this->parseLine($line)); + } + } + + /** + * @param string $line + * + * @return null|string + */ + protected function parseLine($line) + { + $bit = explode(': ', $line); + $line1 = explode('] ', $bit[0]); + + if (!$line1[0]) { + return null; + } + + $line2 = explode(' - ', $bit[1]); + + $date = $line1[0] . ']'; + $type = str_replace('grav.', '', $line1[1]); + $color = $this->colors[$type]; + $error = $line2[0]; + $trace = implode(': ', array_slice($bit, 2)); + + $output = []; + + $output[] = ''; + $output[] = '' . $date . ''; + $output[] = sprintf(' <%s>%s ' . $error . '', $color, $type, $color); + + if ($this->options['trace']) { + $output[] = ' TRACE: '; + $output[] = ' ' . $trace; + } + + $output[] = '' . str_repeat('-', strlen($date)) . ''; + + return implode("\n", $output); + } +} + diff --git a/plugins/error/composer.json b/plugins/error/composer.json new file mode 100644 index 0000000..27ddfa9 --- /dev/null +++ b/plugins/error/composer.json @@ -0,0 +1,39 @@ +{ + "name": "getgrav/grav-plugin-error", + "type": "grav-plugin", + "description": "Error plugin for Grav CMS", + "keywords": ["error", "plugin"], + "homepage": "https://github.com/getgrav/grav-plugin-error", + "license": "MIT", + "authors": [ + { + "name": "Team Grav", + "email": "devs@getgrav.org", + "homepage": "https://getgrav.org", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/getgrav/grav-plugin-error/issues", + "irc": "https://chat.getgrav.org", + "forum": "https://getgrav.org/forum", + "docs": "https://github.com/getgrav/grav-plugin-error/blob/master/README.md" + }, + "autoload": { + "psr-4": { + "Grav\\Plugin\\Console\\": "cli/" + }, + "classmap": [ + "error.php" + ] + }, + "config": { + "platform": { + "php": "7.1.3" + } + }, + "scripts": { + "test": "vendor/bin/codecept run unit", + "test-windows": "vendor\\bin\\codecept run unit" + } +} diff --git a/plugins/error/error.php b/plugins/error/error.php new file mode 100644 index 0000000..edbaaff --- /dev/null +++ b/plugins/error/error.php @@ -0,0 +1,117 @@ + [ + ['autoload', 100000], + ], + 'onPageNotFound' => [ + ['onPageNotFound', 0] + ], + 'onGetPageTemplates' => [ + ['onGetPageTemplates', 0] + ], + 'onTwigTemplatePaths' => [ + ['onTwigTemplatePaths', -10] + ], + 'onDisplayErrorPage.404'=> [ + ['onDisplayErrorPage404', -1] + ] + ]; + } + + /** + * [onPluginsInitialized:100000] Composer autoload. + * + * @return ClassLoader + */ + public function autoload(): ClassLoader + { + return require __DIR__ . '/vendor/autoload.php'; + } + + /** + * @param Event $event + */ + public function onDisplayErrorPage404(Event $event): void + { + if ($this->isAdmin()) { + return; + } + + $event['page'] = $this->getErrorPage(); + $event->stopPropagation(); + } + + /** + * Display error page if no page was found for the current route. + * + * @param Event $event + */ + public function onPageNotFound(Event $event): void + { + $event->page = $this->getErrorPage(); + $event->stopPropagation(); + } + + /** + * @return PageInterface + * @throws \Exception + */ + public function getErrorPage(): PageInterface + { + /** @var Pages $pages */ + $pages = $this->grav['pages']; + + // Try to load user error page. + $page = $pages->dispatch($this->config->get('plugins.error.routes.404', '/error'), true); + if (!$page) { + // If none provided use built in error page. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . '/pages/error.md')); + $page->title($this->grav['language']->translate('PLUGIN_ERROR.ERROR') . ' ' . $page->header()->http_response_code); + + } + + // Login page may not have the correct Cache-Control header set, force no-store for the proxies. + $cacheControl = $page->cacheControl(); + if (!$cacheControl) { + $page->cacheControl('private, no-cache, must-revalidate'); + } + + return $page; + } + + /** + * Add page template types. + */ + public function onGetPageTemplates(Event $event): void + { + /** @var Types $types */ + $types = $event->types; + $types->register('error'); + } + + /** + * Add current directory to twig lookup paths. + */ + public function onTwigTemplatePaths(): void + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } +} diff --git a/plugins/error/error.yaml b/plugins/error/error.yaml new file mode 100644 index 0000000..0a51d4c --- /dev/null +++ b/plugins/error/error.yaml @@ -0,0 +1,3 @@ +enabled: true +routes: + 404: '/error' diff --git a/plugins/error/hebe.json b/plugins/error/hebe.json new file mode 100644 index 0000000..3ca6ad8 --- /dev/null +++ b/plugins/error/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-error", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/error" + } + ] + } + } + } +} diff --git a/plugins/error/languages.yaml b/plugins/error/languages.yaml new file mode 100644 index 0000000..650e617 --- /dev/null +++ b/plugins/error/languages.yaml @@ -0,0 +1,55 @@ +en: + PLUGIN_ERROR: + ERROR: "Error" + ERROR_MESSAGE: "Woops. Looks like this page doesn't exist." + ROUTE_404: "404 Route" +de: + PLUGIN_ERROR: + ERROR: "Fehler" + ERROR_MESSAGE: "Uuups. Sieht aus als ob diese Seite nicht existiert." +hr: + PLUGIN_ERROR: + ERROR: "Greška" + ERROR_MESSAGE: "Uups. Izgleda da ova stranica ne postoji." +ro: + PLUGIN_ERROR: + ERROR: "Eroare" + ERROR_MESSAGE: "Ooops. Se pare că pagina nu există." +fr: + PLUGIN_ERROR: + ERROR: "Erreur" + ERROR_MESSAGE: "Oups. Il semble que cette page n’existe pas." +it: + PLUGIN_ERROR: + ERROR: "Errore" + ERROR_MESSAGE: "Ooops. A quanto pare, questa pagina non esiste." +ru: + PLUGIN_ERROR: + ERROR: "Ошибка" + ERROR_MESSAGE: "Упс. Похоже, этой страницы не существует." + ROUTE_404: "Маршрут 404" +uk: + PLUGIN_ERROR: + ERROR: "Помилка" + ERROR_MESSAGE: "Упс. Схоже, цієї сторінки не існує." + ROUTE_404: "Маршрут 404" +da: + PLUGIN_ERROR: + ERROR: "Fejl" + ERROR_MESSAGE: "Ups. Det ser ud til at siden ikke eksisterer." +zh: + PLUGIN_ERROR: + ERROR: "错误" + ERROR_MESSAGE: "呃,似乎这个页面不存在。" +cs: + PLUGIN_ERROR: + ERROR: "Chyba" + ERROR_MESSAGE: "A jéje. Vypadá to, že hledaná stránka tu není." +pl: + PLUGIN_ERROR: + ERROR: "Błąd" + ERROR_MESSAGE: "Ups. Wygląda na to, że ta strona nie istnieje." +ca: + PLUGIN_ERROR: + ERROR: "Error" + ERROR_MESSAGE: "Ups. Sembla que aquesta pàgina no existeix." diff --git a/plugins/error/pages/error.md b/plugins/error/pages/error.md new file mode 100644 index 0000000..e9b344b --- /dev/null +++ b/plugins/error/pages/error.md @@ -0,0 +1,14 @@ +--- +title: Page not Found +robots: noindex,nofollow +template: error +routable: false +http_response_code: 404 +twig_first: true +process: + twig: true +expires: 0 +--- + +{{ 'PLUGIN_ERROR.ERROR_MESSAGE'|t }} + diff --git a/plugins/error/templates/error.html.twig b/plugins/error/templates/error.html.twig new file mode 100644 index 0000000..420702b --- /dev/null +++ b/plugins/error/templates/error.html.twig @@ -0,0 +1,3 @@ +

    {{ 'PLUGIN_ERROR.ERROR'|t }} {{ header.http_response_code }}

    + +

    {{ page.content|raw }}

    diff --git a/plugins/error/templates/error.json.twig b/plugins/error/templates/error.json.twig new file mode 100644 index 0000000..27472f1 --- /dev/null +++ b/plugins/error/templates/error.json.twig @@ -0,0 +1 @@ +{{ page.content|json_encode()|raw }} \ No newline at end of file diff --git a/plugins/error/vendor/autoload.php b/plugins/error/vendor/autoload.php new file mode 100644 index 0000000..b30c4e0 --- /dev/null +++ b/plugins/error/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/plugins/error/vendor/composer/LICENSE b/plugins/error/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/plugins/error/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/plugins/error/vendor/composer/autoload_classmap.php b/plugins/error/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..71c4666 --- /dev/null +++ b/plugins/error/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $baseDir . '/error.php', +); diff --git a/plugins/error/vendor/composer/autoload_namespaces.php b/plugins/error/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/plugins/error/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/cli'), +); diff --git a/plugins/error/vendor/composer/autoload_real.php b/plugins/error/vendor/composer/autoload_real.php new file mode 100644 index 0000000..ee6a4c0 --- /dev/null +++ b/plugins/error/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit4e2f762c713c4d4aae8969c74f5623a3::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/plugins/error/vendor/composer/autoload_static.php b/plugins/error/vendor/composer/autoload_static.php new file mode 100644 index 0000000..11ca02a --- /dev/null +++ b/plugins/error/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'Grav\\Plugin\\Console\\' => 20, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Grav\\Plugin\\Console\\' => + array ( + 0 => __DIR__ . '/../..' . '/cli', + ), + ); + + public static $classMap = array ( + 'Grav\\Plugin\\ErrorPlugin' => __DIR__ . '/../..' . '/error.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit4e2f762c713c4d4aae8969c74f5623a3::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit4e2f762c713c4d4aae8969c74f5623a3::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit4e2f762c713c4d4aae8969c74f5623a3::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/error/vendor/composer/installed.json b/plugins/error/vendor/composer/installed.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/plugins/error/vendor/composer/installed.json @@ -0,0 +1 @@ +[] diff --git a/plugins/feed/CHANGELOG.md b/plugins/feed/CHANGELOG.md new file mode 100644 index 0000000..9ef1ad6 --- /dev/null +++ b/plugins/feed/CHANGELOG.md @@ -0,0 +1,143 @@ +# v1.8.5 +## 06/09/2021 + +1. [](#improved) + * Rolled back the URL check functionality as it caused more issues than it solved. + +# v1.8.4 +## 06/07/2021 + +1. [](#improved) + * Added a configurable `enable_url_check` (default `true`) to disable the URL checking if you run into an issue. +1. [](#bugfix) + * More robust URL checking including multi-lang versions [#62](https://github.com/getgrav/grav-plugin-feed/issues/62) + +# v1.8.3 +## 05/28/2021 + +1. [](#bugfix) + * Fixed issue with feeds at the root of a site [#61](https://github.com/getgrav/grav-plugin-feed/issues/61) + +# v1.8.2 +## 05/21/2021 + +1. [](#bugfix) + * Fixed issue with json feed type corrupting other json requests [getgrav/grav-premium-issues#102](https://github.com/getgrav/grav-premium-issues/issues/102) + * Fixed issue with modular pages showing up in feed + + +# v1.8.1 +## 05/21/2021 + +1. [](#bugfix) + * Provide a default language if multi-language not enabled + +# v1.8.0 +## 12/02/2020 + +1. [](#new) + * Require Grav v1.6 + * Pass phpstan level 1 tests +1. [](#improved) + * Major plugin overhaul [#57](https://github.com/getgrav/grav-plugin-feed/pull/57) + * Bumped the default image sizes in atom/rss + +# v1.7.1 +## 05/09/2019 + +1. [](#bugfix) + * Fix issue with Feed Options not showing up in Quark (and other themes) [#46](https://github.com/getgrav/grav-plugin-feed/issues/46) + +# v1.7.0 +## 04/15/2019 + +1. [](#improved) + * Use `safe_truncate_html` [#41](https://github.com/getgrav/grav-plugin-feed/pull/41) + * Allow full-text feeds [#37](https://github.com/getgrav/grav-plugin-feed/pull/37) + * Dynamic json feed header and image file support [#32](https://github.com/getgrav/grav-plugin-feed/pull/32) + * Added `json` link example to `README.md` +1. [](#bugfix) + * Changed type `text` to `range` to prevent validation errors [#45](https://github.com/getgrav/grav-plugin-feed/issues/45) + * Always show route in url for self-link [#38](https://github.com/getgrav/grav-plugin-feed/pull/38) + +# v1.6.2 +## 06/06/2017 + +1. [](#bugfix) + * Fix issue with feeds not rendering with cache enabled [#27](https://github.com/getgrav/grav-plugin-feed/pull/27) + +# v1.6.1 +## 05/30/2017 + +1. [](#improved) + * Improved JSON template to `json_encode()` all output +1. [](#bugfix) + * Optimized logic to disable JSON feeds by default and only set the template when there's a collection + +# v1.6.0 +## 05/25/2017 + +1. [](#new) + * Added support for new JSON feed format by @RosemaryOrchard [#21](https://github.com/getgrav/grav-plugin-feed/pull/21) + +# v1.5.3 +## 04/12/2017 + +1. [](#bugfix) + * Fix a truncate issue [#16](https://github.com/getgrav/grav-plugin-feed/pull/16) + +# v1.5.2 +## 02/17/2017 + +1. [](#bugfix) + * Fix issue on non-collection pages [#14](https://github.com/getgrav/grav-plugin-feed/pull/14) + +# v1.5.1 +## 01/24/2017 + +1. [](#bugfix) + * Add support for Twig `Autoescape variables` mode + +# v1.5.0 +## 07/14/2016 + +1. [](#improved) + * Make Feeds 'language-safe' + +# v1.4.1 +## 10/07/2015 + +1. [](#bugfix) + * Avoid duplicated routes + +# v1.4.0 +## 08/26/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.3.3 +## 03/24/2015 + +1. [](#improved) + * Feed will now skip pages with `feed: skip: true` in frontmatter +1. [](#bugfix) + * Fixed page overrides for configuration + +# v1.3.2 +## 02/19/2015 + +1. [](#bugfix) + * Fixed couple of RSS validation issues + +# v1.3.1 +## 12/26/2014 + +1. [](#bugfix) + * Fixed issue with default configuration not being loaded yet + +# v1.3.0 +## 11/30/2014 + +1. [](#new) + * ChangeLog started... diff --git a/plugins/feed/LICENSE b/plugins/feed/LICENSE new file mode 100644 index 0000000..484793a --- /dev/null +++ b/plugins/feed/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +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/plugins/feed/README.md b/plugins/feed/README.md new file mode 100644 index 0000000..580acc7 --- /dev/null +++ b/plugins/feed/README.md @@ -0,0 +1,146 @@ +# Grav Syndication Feed Plugin + +![Feed](assets/readme_1.png) + +`feed` is a [Grav](http://github.com/getgrav/grav) plugin and allows Grav to generate feeds of your pages. + +This plugin supports __Atom 1.0__, __RSS__ and __JSON__ feed types. Enabling is very simple. just install this plugin in the `/user/plugins/` folder in your Grav install. By default, the plugin is enabled and provides some default values. + +| NOTE: JSON feeds must be enabled manually in the plugin configuration as the `.json` extension is commonly used and this can conflict with other plugins. + +If you do enable the JSON feed, you will want to edit `feed.json.twig`. Replace the placeholder data on lines 2 and 3 (`feed_url` and `author:url`) with your own data. You may also want to change the formatting of the date on lines 8 and 9. + +# Installation + +Installing the Feed plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install feed + +This will install the Feed plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/feed`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `feed`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-feed) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/feed + +>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate. + +# Usage + +The feeds work for pages that have sub-pages, for example a blog list view. If your page has a `content`, then the RSS plugin will automatically be enabled. Simply append either `.atom`, `.rss` or `.json` to the url. For example, if you have a blog page that defines a `content` header to display a list of blog pages in a list, and the URL is `http://www.mysite.com/blog` then the feed would simply be: + +``` +http://www.mysite.com/blog.atom +``` + +or + +``` +http://www.mysite.com/blog.rss +``` + +or + +``` +http://www.mysite.com/blog.json +``` + +## Autodiscovery + +To let feed readers discover the feed automatically, add a link to your HTML `` tag: + +``` + + + +``` + +## Creating Feed Buttons in Your Pages + +Just having the plugin loaded and activated is enough to get it working, but you can help users find your feeds by creating buttons in the body of your page users can easily discover and click on to access the feeds. + +In our [Blog Skeleton] demo, you will see these buttons located in the sidebar under the heading `Syndicate`. This was done very easily by adding the following information to the `sidebar.html.twig` template file found under `/user/themes/antimatter/templates/partials/`. + +``` + Atom 1.0 + RSS +``` + +The first line adds the **Atom** feed by simply adding `.atom` to the base URL of the site, while the second handles RSS. This a very simple way to add a useful feature to your site that your visitors will enjoy. + +# Config Defaults + +``` +enabled: true +limit: 10 +title: 'My Feed Title' +description: 'My Feed Description' +length: 500 +enable_json_feed: false +show_last_modified: false +``` + +You can override any of the default values by setting one or more of these in your blog list page where `sub_pages` is defined. For example: + +``` +title: Sample Blog +content: + items: @self.children + limit: 5 + pagination: true +feed: + limit: 15 + description: Sample Blog Description +``` + +You can ensure a particular page is skipped from the feed by adding the following in the frontmatter header: + +``` +title: Sample Blog +feed: + skip: true +``` + +# Updating + +As development for the Feed plugin continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Feed is easy, and can be done through Grav's GPM system, as well as manually. + +## GPM Update (Preferred) + +The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: + + bin/gpm update feed + +This command will check your Grav install to see if your Feed plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. + +## Manual Update + +Manually updating Feed is pretty simple. Here is what you will need to do to get this done: + +* Delete the `your/site/user/plugins/feed` directory. +* Downalod the new version of the Feed plugin from either [GitHub](https://github.com/getgrav/grav-plugin-feed) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras). +* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `feed`. +* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`. + +> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. + +## Nginx Note: + +If you are having trouble with 404s with Nginx, it might be related to your configuration. You may need to remove the feed extensions from the list of types to cache as static files: `.xml`, `.rss`, and `.atom`. For example: + +```nginx +# Cache static files +location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf)$ { + add_header "Access-Control-Allow-Origin" "*"; + access_log off; + log_not_found off; + expires max; +} +``` diff --git a/plugins/feed/assets/readme_1.png b/plugins/feed/assets/readme_1.png new file mode 100644 index 0000000..41843dc Binary files /dev/null and b/plugins/feed/assets/readme_1.png differ diff --git a/plugins/feed/blueprints.yaml b/plugins/feed/blueprints.yaml new file mode 100644 index 0000000..9d99c83 --- /dev/null +++ b/plugins/feed/blueprints.yaml @@ -0,0 +1,78 @@ +name: Feed +type: plugin +slug: feed +version: 1.8.5 +description: The **Feed** plugin is a simple yet powerful add-on that lets you view a Grav Collection as **JSON**, **RSS** or **Atom** news feed. +icon: rss +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-feed +demo: http://demo.getgrav.org/blog-skeleton +keywords: feed, plugin, rss, atom, collection, json +bugs: https://github.com/getgrav/grav-plugin-feed/issues +license: MIT +dependencies: + - { name: grav, version: '>=1.6.0' } + +form: + validation: strict + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + limit: + type: range + label: Feed count + validate: + type: number + min: 10 + max: 1000 + + title: + type: text + label: Title + + description: + type: textarea + label: Description + + length: + type: range + label: Feed Length (0 for full-text feed) + validate: + type: number + min: 0 + max: 10000 + + enable_json_feed: + type: toggle + label: JSON feed support + highlight: 0 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + show_last_modified: + type: toggle + label: Show last modified date + help: If enabled, file modification date will be used for computing "last updated" times in feeds + highlight: 0 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool diff --git a/plugins/feed/blueprints/feed.yaml b/plugins/feed/blueprints/feed.yaml new file mode 100644 index 0000000..1269f39 --- /dev/null +++ b/plugins/feed/blueprints/feed.yaml @@ -0,0 +1,22 @@ +form: + fields: + tabs: + fields: + blog: + type: tab + + fields: + header.feed.limit: + type: text + label: Feed count + default: 10 + validate: + type: int + + header.feed.title: + type: text + label: Feed title + + header.feed.description: + type: text + label: Feed description diff --git a/plugins/feed/composer.json b/plugins/feed/composer.json new file mode 100644 index 0000000..c09e395 --- /dev/null +++ b/plugins/feed/composer.json @@ -0,0 +1,36 @@ +{ + "name": "getgrav/grav-plugin-feed", + "type": "grav-plugin", + "description": "Feed plugin for Grav CMS", + "keywords": ["feed", "plugin"], + "homepage": "https://github.com/getgrav/grav-plugin-feed", + "license": "MIT", + "authors": [ + { + "name": "Team Grav", + "email": "devs@getgrav.org", + "homepage": "https://getgrav.org", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/getgrav/grav-plugin-feed/issues", + "irc": "https://chat.getgrav.org", + "forum": "https://getgrav.org/forum", + "docs": "https://github.com/getgrav/grav-plugin-feed/blob/master/README.md" + }, + "autoload": { + "classmap": [ + "feed.php" + ] + }, + "config": { + "platform": { + "php": "7.1.3" + } + }, + "scripts": { + "test": "vendor/bin/codecept run unit", + "test-windows": "vendor\\bin\\codecept run unit" + } +} diff --git a/plugins/feed/composer.lock b/plugins/feed/composer.lock new file mode 100644 index 0000000..26adc9c --- /dev/null +++ b/plugins/feed/composer.lock @@ -0,0 +1,21 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "76fd53c3c430250c751e8cb0d82e5308", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "7.1.3" + }, + "plugin-api-version": "2.0.0" +} diff --git a/plugins/feed/feed.php b/plugins/feed/feed.php new file mode 100644 index 0000000..f89fafd --- /dev/null +++ b/plugins/feed/feed.php @@ -0,0 +1,162 @@ + [ + ['autoload', 100000], + ['onPluginsInitialized', 0], + ], + 'onBlueprintCreated' => ['onBlueprintCreated', 0] + ]; + } + + /** + * [onPluginsInitialized:100000] Composer autoload. + * + * @return ClassLoader + */ + public function autoload() + { + return require __DIR__ . '/vendor/autoload.php'; + } + + /** + * Activate feed plugin only if feed was requested for the current page. + * + * Also disables debugger. + */ + public function onPluginsInitialized() + { + if ($this->isAdmin()) { + return; + } + + $this->feed_config = (array) $this->config->get('plugins.feed'); + + if ($this->feed_config['enable_json_feed']) { + $this->valid_types[] = 'json'; + } + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $this->type = $uri->extension(); + + if ($this->type && in_array($this->type, $this->valid_types)) { + + $this->enable([ + 'onPageInitialized' => ['onPageInitialized', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + ]); + } + } + + /** + * Initialize feed configuration. + */ + public function onPageInitialized() + { + $page = $this->grav['page']; + + // Overwrite regular content with feed config, so you can influence the collection processing with feed config + if (property_exists($page->header(), 'content')) { + if (isset($page->header()->feed)) { + $this->feed_config = array_merge($this->feed_config, $page->header()->feed); + } + + $page->header()->content = array_merge($page->header()->content, $this->feed_config); + + $this->grav['twig']->template = 'feed.' . $this->type . '.twig'; + + $this->enable([ + 'onCollectionProcessed' => ['onCollectionProcessed', 0], + ]); + } + + } + + /** + * Feed consists of all sub-pages. + * + * @param Event $event + */ + public function onCollectionProcessed(Event $event) + { + /** @var Collection $collection */ + $collection = $event['collection']->nonModular(); + + foreach ($collection as $slug => $page) { + $header = $page->header(); + if (isset($header->feed) && !empty($header->feed['skip'])) { + $collection->remove($page); + } + } + } + + /** + * Add current directory to twig lookup paths. + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } + + + /** + * Extend page blueprints with feed configuration options. + * + * @param Event $event + */ + public function onBlueprintCreated(Event $event) + { + static $inEvent = false; + + /** @var Data\Blueprint $blueprint */ + $blueprint = $event['blueprint']; + $form = $blueprint->form(); + + $blog_tab_exists = isset($form['fields']['tabs']['fields']['blog']); + + if (!$inEvent && $blog_tab_exists) { + $inEvent = true; + $blueprints = new Data\Blueprints(__DIR__ . '/blueprints/'); + $extends = $blueprints->get('feed'); + $blueprint->extend($extends, true); + $inEvent = false; + } + } +} \ No newline at end of file diff --git a/plugins/feed/feed.yaml b/plugins/feed/feed.yaml new file mode 100644 index 0000000..c998a44 --- /dev/null +++ b/plugins/feed/feed.yaml @@ -0,0 +1,7 @@ +enabled: true +limit: 10 +title: 'My Feed Title' +description: 'My Feed Description' +length: 500 +enable_json_feed: false +show_last_modified: false diff --git a/plugins/feed/templates/feed.atom.twig b/plugins/feed/templates/feed.atom.twig new file mode 100644 index 0000000..0d63a45 --- /dev/null +++ b/plugins/feed/templates/feed.atom.twig @@ -0,0 +1,45 @@ +{# Format specification: https://tools.ietf.org/html/rfc4287 #} +{% set collection = collection|default(page.collection) %} +{% set feed_updated = 0 %} +{% for page in collection %} + {%- set feed_updated = max(feed_updated, page.date) %} + {%- if collection.params.show_last_modified %} + {%- set feed_updated = max(feed_updated, page.modified) %} + {%- endif %} +{% endfor %} + + + {{ collection.params.title }} + + {{ collection.params.description }} + {{ feed_updated|date("Y-m-d\\TH:i:sP") }} + + {{ site.author.name }} + + {{ page.url(true) }} + {% for item in collection %} + {% set banner = item.media.images|first %} + + {{ item.title|e }} + {{ item.url(true) }} + {% if collection.params.show_last_modified %} + {{ item.modified|date("Y-m-d\\TH:i:sP") }} + {% else %} + {{ item.date|date("Y-m-d\\TH:i:sP") }} + {% endif %} + {{ item.date|date("Y-m-d\\TH:i:sP") }} + + {% for tag in item.taxonomy.tag %} + + {% endfor %} + + + + + {% endfor %} + diff --git a/plugins/feed/templates/feed.json.twig b/plugins/feed/templates/feed.json.twig new file mode 100644 index 0000000..c50adbc --- /dev/null +++ b/plugins/feed/templates/feed.json.twig @@ -0,0 +1,43 @@ +{# Format specification: https://www.jsonfeed.org/version/1/ #} +{% set collection = collection|default(page.collection) %} +{% set jsonfeed = { + "version" : "https://jsonfeed.org/version/1", + "title": collection.params.title, + "home_page_url": page.url(true), + "feed_url": uri.rootUrl(true)~uri.uri(), + "description": collection.params.description, + "author": {"name": site.author.name} +} %} + +{% set itemList = [] %} +{% for item in collection %} + {%- set post = { + "title": item.title|e, + "date_published": item.date|date('Y-m-d\\TH:i:sP'), + "id": item.url(true), + "url": item.url(true), + "content_html": item.content|safe_truncate_html(collection.params.length) + } %} + {% set banner = item.media.images|first %} + + {% if item.header.metadata.description %} + {%- set post = post|merge({"summary": item.header.metadata.description|e}) %} + {% endif %} + + {% if collection.params.show_last_modified %} + {%- set post = post|merge({"date_modified": item.modified|date('Y-m-d\\TH:i:sP')}) %} + {% endif %} + + {% if item.taxonomy.tag %} + {%- set post = post|merge({"tags": item.taxonomy.tag}) %} + {% endif %} + + {% set image = item.media.images|first %} + {% if image %} + {%- set post = post|merge({"image": image.url(true)}) %} + {% endif %} + {%- set itemList = itemList|merge([post]) %} +{% endfor %} + +{% set jsonfeed = jsonfeed|merge({"items": itemList}) %} +{{- jsonfeed|json_encode|raw }} diff --git a/plugins/feed/templates/feed.rss.twig b/plugins/feed/templates/feed.rss.twig new file mode 100644 index 0000000..c2000b5 --- /dev/null +++ b/plugins/feed/templates/feed.rss.twig @@ -0,0 +1,40 @@ +{# Format specification: https://www.rssboard.org/rss-specification #} +{% set collection = collection|default(page.collection) %} +{% set lastBuildDate = 0 %} +{% for page in collection %} + {%- set lastBuildDate = max(lastBuildDate, page.date) %} + {%- if collection.params.show_last_modified %} + {%- set lastBuildDate = max(feed_updated, page.modified) %} + {%- endif %} +{% endfor %} + + + + {{ collection.params.title }} + {{ page.url(true) }} + + {{ collection.params.description }} + {{ grav.language.getLanguage|default(config.system.language.default_lang)|default('en') }} + {{ lastBuildDate|date('D, d M Y H:i:s O') }} + {% for item in collection %} + {% set banner = item.media.images|first %} + + {{ item.title|e }} + {{ item.url(true) }} + {{ item.url(true) }} + {{ item.date|date('D, d M Y H:i:s O') }} + + + + {% for tag in item.taxonomy.tag %} + {{ tag|e }} + {% endfor %} + + {% endfor %} + + diff --git a/plugins/feed/vendor/autoload.php b/plugins/feed/vendor/autoload.php new file mode 100644 index 0000000..7273375 --- /dev/null +++ b/plugins/feed/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/plugins/feed/vendor/composer/InstalledVersions.php b/plugins/feed/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..0766f58 --- /dev/null +++ b/plugins/feed/vendor/composer/InstalledVersions.php @@ -0,0 +1,209 @@ + + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '8f9e4fdbb1ae743f788767897571f175fcdc4e27', + 'name' => 'getgrav/grav-plugin-feed', + ), + 'versions' => + array ( + 'getgrav/grav-plugin-feed' => + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '8f9e4fdbb1ae743f788767897571f175fcdc4e27', + ), + ), +); + + + + + + + +public static function getInstalledPackages() +{ +return array_keys(self::$installed['versions']); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +return isset(self::$installed['versions'][$packageName]); +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +$ranges = array(); +if (isset(self::$installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = self::$installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + + + + + +public static function getVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['version']; +} + + + + + +public static function getPrettyVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['pretty_version']; +} + + + + + +public static function getReference($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['reference'])) { +return null; +} + +return self::$installed['versions'][$packageName]['reference']; +} + + + + + +public static function getRootPackage() +{ +return self::$installed['root']; +} + + + + + + + +public static function getRawData() +{ +return self::$installed; +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +} +} diff --git a/plugins/feed/vendor/composer/LICENSE b/plugins/feed/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/plugins/feed/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/plugins/feed/vendor/composer/autoload_classmap.php b/plugins/feed/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..235977e --- /dev/null +++ b/plugins/feed/vendor/composer/autoload_classmap.php @@ -0,0 +1,11 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Grav\\Plugin\\FeedPlugin' => $baseDir . '/feed.php', +); diff --git a/plugins/feed/vendor/composer/autoload_namespaces.php b/plugins/feed/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/plugins/feed/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitd512066232734940b99b34ad4f8603bc::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/plugins/feed/vendor/composer/autoload_static.php b/plugins/feed/vendor/composer/autoload_static.php new file mode 100644 index 0000000..0a7962c --- /dev/null +++ b/plugins/feed/vendor/composer/autoload_static.php @@ -0,0 +1,21 @@ + __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Grav\\Plugin\\FeedPlugin' => __DIR__ . '/../..' . '/feed.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInitd512066232734940b99b34ad4f8603bc::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/feed/vendor/composer/installed.json b/plugins/feed/vendor/composer/installed.json new file mode 100644 index 0000000..87fda74 --- /dev/null +++ b/plugins/feed/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": true, + "dev-package-names": [] +} diff --git a/plugins/feed/vendor/composer/installed.php b/plugins/feed/vendor/composer/installed.php new file mode 100644 index 0000000..987a994 --- /dev/null +++ b/plugins/feed/vendor/composer/installed.php @@ -0,0 +1,24 @@ + + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '8f9e4fdbb1ae743f788767897571f175fcdc4e27', + 'name' => 'getgrav/grav-plugin-feed', + ), + 'versions' => + array ( + 'getgrav/grav-plugin-feed' => + array ( + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'aliases' => + array ( + ), + 'reference' => '8f9e4fdbb1ae743f788767897571f175fcdc4e27', + ), + ), +); diff --git a/plugins/flex-objects/.eslintrc b/plugins/flex-objects/.eslintrc new file mode 100644 index 0000000..e62c1d5 --- /dev/null +++ b/plugins/flex-objects/.eslintrc @@ -0,0 +1,170 @@ +{ + "root": true, + "env": { + "browser": true, + "node": true, + "es6": true + }, + + "parser": "@babel/eslint-parser", + + "parserOptions": { + "ecmaVersion": 7, + "sourceType": "module", + "requireConfigFile": false + }, + + "rules": { + "accessor-pairs": 2, + "array-bracket-spacing": 0, + "block-scoped-var": 0, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "camelcase": 0, + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "complexity": 0, + "computed-property-spacing": 0, + "consistent-return": 0, + "consistent-this": 0, + "constructor-super": 2, + "curly": [2, "multi-line"], + "default-case": 0, + "dot-location": [2, "property"], + "dot-notation": 0, + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "func-names": 0, + "func-style": 0, + "generator-star-spacing": [2, { "before": true, "after": true }], + "guard-for-in": 0, + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 4, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "linebreak-style": 0, + "lines-around-comment": 0, + "max-nested-callbacks": 0, + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "newline-after-var": 0, + "no-alert": 0, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 0, + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 0, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 0, + "no-empty": 0, + "no-empty-character-class": 2, + "no-eq-null": 0, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 0, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inline-comments": 0, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 0, + "no-loop-func": 0, + "no-mixed-requires": 0, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 0, + "no-new": 2, + "no-new-func": 0, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-param-reassign": 0, + "no-path-concat": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-restricted-modules": 0, + "no-return-assign": 2, + "no-script-url": 0, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 0, + "no-underscore-dangle": 0, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": 0, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-use-before-define": 0, + "no-var": 0, + "no-void": 0, + "no-warning-comments": 0, + "no-with": 2, + "object-curly-spacing": 0, + "object-shorthand": 0, + "one-var": [2, { "initialized": "never" }], + "operator-assignment": 0, + "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": 0, + "prefer-const": 0, + "quote-props": 0, + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "always"], + "semi-spacing": 0, + "sort-vars": 0, + "keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }], + "strict": 0, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "any"], + "wrap-regex": 0, + "yoda": [2, "never"] + } +} diff --git a/plugins/flex-objects/.gitignore b/plugins/flex-objects/.gitignore new file mode 100644 index 0000000..0ac92e1 --- /dev/null +++ b/plugins/flex-objects/.gitignore @@ -0,0 +1,3 @@ +.idea +.DS_Store +node_modules diff --git a/plugins/flex-objects/CHANGELOG.md b/plugins/flex-objects/CHANGELOG.md new file mode 100644 index 0000000..966c4f3 --- /dev/null +++ b/plugins/flex-objects/CHANGELOG.md @@ -0,0 +1,469 @@ +# v1.1.6 +## 11/29/2021 + +2. [](#bugfix) + * Fixed regression `Call to a member function getRoute() on null` when using CLI [#151](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/151) + +# v1.1.5 +## 11/24/2021 + +1. [](#new) + * Added method `ObjectController::checkAuthorizations()` to check if one of the actions is true +2. [](#bugfix) + * Fixed regression when calling flex router with a path + +# v1.1.4 +## 11/16/2021 + +1. [](#new) + * Require **Grav 1.7.25** +1. [](#improved) + * Changed flex router not to trigger `onPageNotFound` event + * Changed flex router to be called also with empty path + * If ACL check for the object fails, display unauthorized page instead of 404 +1. [](#bugfix) + * Fixed unescaped messages in JSON responses + * Fixed `Call to a member function getName() on null` when using file field [#149](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/149) + +# v1.1.3 +## 10/26/2021 + +1. [](#improved) + * Updated JS dependencies to latest + * Optimized import of certain JS dependencies + * Dev: Moved away from deprecated UglifyJsPlugin in favor of TerserPlugin + * Use active form from the Form plugin to get page metadata + * Added page header `flex.access.override: true`, which allows flex to replace page `access` when user is allowed to perform action in flex +1. [](#bugfix) + * Fixed flex object page access for super users when permission was denied + +# v1.1.2 +## 09/14/2021 + +1. [](#new) + * Require **Grav 1.7.21**, optionally **Error 1.8.0**, **Login 3.5.2** and **Form 5.1.1** + * Added file upload/delete support to frontend forms + * Support proper error, login and unauthorized pages if all requirements are met + * Added page header `flex.router: [ROUTER]` which triggers `flex.router.[ROUTER]` event for child routes of the page + * Added `flex.[type].task.create.after`, `flex.[type].task.update.after` and `flex.[type].task.delete.after` events for frontend + +# v1.1.1 +## 09/01/2021 + +1. [](#bugfix) + * Fixed XSS in page admin + * Fixed check for bad folder name, prevent bad characters + +# v1.1.0 +## 08/31/2021 + +1. [](#new) + * Require **Grav 1.7.19** and **Form 5.1.0** + * Added basic frontend editing support + * Added `onBeforeFlexFormInitialize` event to help to initialize the frontend form +1. [](#bugfix) + * Fixed error in admin when field validation fails + +# v1.0.16 +## 07/19/2021 + +1. [](#new) + * Added basic new modal support for all flex types +1. [](#bugfix) + * Fixed authorization check for user configuration + +# v1.0.15 +## 06/16/2021 + +1. [](#improved) + * Better checks against missing Flex Type inside tasks + * Better authorization checks, falls back to directory level authorization checks if objects do not support authorization +1. [](#bugfix) + * Fixed missing handling of child_type in Flex Pages [getgrav/grav-plugin-admin#2087](https://github.com/getgrav/grav-plugin-admin/issues/2087) + * Added support for multiple `Exports` in a dropdown + * Admin is no longer a dependency of Flex Objects [#130](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/130) + * Fixed authorization checks during page creation for users who have limited access to some pages [getgrav/grav#3382](https://github.com/getgrav/grav/issues/3382) + * Fixed permission check when moving a page [getgrav/grav#3382](https://github.com/getgrav/grav/issues/3382) + +# v1.0.14 +## 06/07/2021 + +1. [](#improved) + * Added enhanced copy modal from Pages list [getgrav/grav-plugin-admin#2139](https://github.com/getgrav/grav-plugin-admin/issues/2139) + +# v1.0.13 +## 06/03/2021 + +1. [](#bugfix) + * Fixed expert mode for Flex Pages + +# v1.0.12 +## 06/02/2021 + +1. [](#bugfix) + * Fixed logic to get form blueprints and object, prevents events from being fired twice + * Fixed breadcrumb item in Pages list not translating HTML entities [#127](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/127) + +# v1.0.11 +## 05/24/2021 + +1. [](#improved) + * Allow file uploads to send data such as `data[media_order]` + +# v1.0.10 +## 05/19/2021 + +1. [](#bugfix) + * Fixed `Add Folder` not updating the page list until cache is cleared + * Fixed broken error message translations + +# v1.0.9 +## 04/29/2021 + +1. [](#bugfix) + * Fixed fatal error when copying a page in admin if no modal is being shown [getgrav/grav#3335](https://github.com/getgrav/grav/issues/3335) + +# v1.0.8 +## 04/23/2021 + +1. [](#new) + * Require **Admin 1.10.13** + * Require **Form Plugin 5.0.2** +1. [](#improved) + * Added a few missing translations + * Utilize new Admin detector to prevent Save actions that triggers unsaved notice on unload [getgrav/grav-plugin-admin#2125](https://github.com/getgrav/grav-plugin-admin/issues/2125) + * Improved copying page by adding a modal for new page title and folder name + +# v1.0.7 +## 04/06/2021 + +1. [](#new) + * Require **Grav 1.7.10** + * Added deny option support to `filepicker` field [#119](https://github.com/trilbymedia/grav-plugin-flex-objects/pull/119) +1. [](#bugfix) + * Prevent expert editing mode from anyone else than super users [grav-plugin-admin#2094](https://github.com/getgrav/grav-plugin-admin/issues/2094) + * Fixed not being able to add new folder [grav#3293](https://github.com/getgrav/grav/issues/3293) + * Fixed Flex directories defined only in theme not showing up [grav#3292](https://github.com/getgrav/grav/issues/3292) + +# v1.0.6 +## 03/30/2021 + +1. [](#bugfix) + * Fixed automatic git-sync in admin save and delete [#120](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/120) + * Prevent Add Page / Add Module modals from closing if clicking on the outside overlay [grav-plugin-admin#2089](https://github.com/getgrav/grav-plugin-admin/issues/2089) + +# v1.0.5 +## 03/19/2021 + +1. [](#new) + * Require **Grav 1.7.9** + * Require **Form Plugin 5.0.1** +1. [](#improved) + * Catch JSON decoding issues in controllers +1. [](#bugfix) + * Fixed broken media upload/picker fields with `@self/path` notations [grav#3275](https://github.com/getgrav/grav/issues/3275) + * Fixed `filepicker` field not including newly uploaded and excluding newly deleted files before saving the object + * Fixed `Flex Page` CRUD ACL when creating a new page [#115](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/115) + * Bumped dependencies versions [#116](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/116) + * Fixed clicking `move` button on some pages resulting in endless loading spinner [grav-plugin-admin#2095](https://github.com/getgrav/grav-plugin-admin/issues/2095) + +# v1.0.4 +## 03/17/2021 + +1. [](#improved) + * Added id attributes for buttons to help on acceptance testing +1. [](#bugfix) + * Fixed fatal error in `/admin/flex-objects` [#114](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/114) + * Fixed `onAdminSave` original page having empty header [grav#3259](https://github.com/getgrav/grav/issues/3259) + * Fixed flash issues on uploading files into a new page + +# v1.0.3 +## 02/17/2021 + +1. [](#improved) + * List field: added new `placement` property to decide whether to add new items at the top, bottom or based on the *position* of the clicked button [#105](https://github.com/trilbymedia/grav-plugin-flex-objects/pull/105) + * Added default styling for Flex-Objects Admin list view +1. [](#bugfix) + * Fixed fatal error if configuration is missing directories [#107](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/107) + * Fixed case-sensitive `accept` in `filepicker` field + * Fixed pages admin being accessible without read/write permissions [grav-plugin-admin#2053](https://github.com/getgrav/grav-plugin-admin/issues/2053) + * Fixed missing event `onAdminCreatePageFrontmatter` when creating a new page [grav-plugin-auto-date#8](https://github.com/getgrav/grav-plugin-auto-date/issues/8) + * Fixed missing event `onAdminAfterDelMedia` when deleting a file from a page + * Fixed filepicker support for old `theme@:/` and `page@:/` notations [#109](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/109) + * Fixed adding the same new page twice remembering content from the last try + * Fixed saving a new page with invalid data makes blueprint fields disappear [grav-plugin-admin#2068](https://github.com/getgrav/grav-plugin-admin/issues/2068) + +# v1.0.2 +## 02/01/2021 + +1. [](#new) + * Require **Grav 1.7.4** +1. [](#bugfix) + * Fixed saving page in expert mode [grav#3174](https://github.com/getgrav/grav/issues/3174) + +# v1.0.1 +## 01/20/2021 + +1. [](#bugfix) + * Fixed 404 when trying to edit a page with accented characters [grav-plugin-admin#2026](https://github.com/getgrav/grav-plugin-admin/issues/2026) + +# v1.0.0 +## 01/19/2021 + +1. [](#new) + * Added `$grav['flex_objects']->getAdminController()` method +1. [](#improved) + * Added support for relative paths in `getLevelListing` action +1. [](#bugfix) + * Fixed admin not working with types that do not implement `FlexAuthorizeInterface` + * Fixed bad redirect when creating new flex object and choosing to create another return to the list + * Fixed bad redirect when changing parent of new page and saving [grav-plugin-admin#2014](https://github.com/getgrav/grav-plugin-admin/issues/2014) + * Fixed page forms being empty if multi-language is enabled, but there's just one language [grav#3147](https://github.com/getgrav/grav/issues/3147) + * Fixed copying a page within a parent with no create permission [grav-plugin-admin#2002](https://github.com/getgrav/grav-plugin-admin/issues/2002) + +# v1.0.0-rc.20 +## 12/15/2020 + +1. [](#improved) + * Default cookies usage to SameSite Lax [grav-plugin-admin#1998](https://github.com/getgrav/grav-plugin-admin/issues/1998) + * Fixed typo [#89](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/89) + +# v1.0.0-rc.19 +## 12/02/2020 + +1. [](#improved) + * Just keeping sync with Grav rc.19 + +# v1.0.0-rc.18 +## 12/02/2020 + +1. [](#new) + * Require **PHP 7.3.6** +1. [](#improved) + * Improved frontend templates + * Improve blueprint structure + * Hooked up Duplicate and Move from within Pages list [#81](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/81) + * Respect CRUD ACL actions for items shortcuts in pages list [#82](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/82) + * Refresh object on controllers to make sure it is up to date +1. [](#bugfix) + * Fixed fatal error in admin if list view hasn't been defined + * Fixed fatal error in admin if directory throws exception + * Fixed attempts to add an existing page + * Fixed form loosing its form state if saving fails when using `ObjectController` + * Fixed missing context when rendering collection in frontend + * Fixed Flex Admin activating on too old Admin plugin versions + +# v1.0.0-rc.17 +## 10/07/2020 + +1. [](#bugfix) + * Fixed media uploads for objects which do not implement `FlexAuthorizeInterface` + * Fixed file picker field not recognizing `folder: @self` variants + +# v1.0.0-rc.16 +## 09/01/2020 + +1. [](#improved) + * Simplified `Flex Pages` admin not to differentiate between default language file extensions [#47](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/47) +1. [](#bugfix) + * Fixed extra space in Flex admin pages + * Fixed folder creation with parent other than root [#66](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/66) + * Fixed task redirects in sub-folder multi-site environments + * Fixed typo in default permissions (should have been `admin.flex-objects`) [grav#2915](https://github.com/getgrav/grav/issues/2915) + +# v1.0.0-rc.15 +## 07/22/2020 + +1. [](#new) + * Released with no changes to keep sync with Grav + Admin + +# v1.0.0-rc.14 +## 07/09/2020 + +1. [](#new) + * Released with no changes to keep sync with Grav + Admin + +# v1.0.0-rc.13 +## 07/01/2020 + +1. [](#bugfix) + * Fixed bad link in directory listing template + * Fixed admin save task displaying error message about non-existing data type + * Fixed `pagemedia` field not uploading/deleting files right away + * Fixed `Flex Pages` add, copy and move buttons appearing in edit view when no permissions + * Fixed `Flex Pages` permission issues + * Fixed some admin redirect issues + +# v1.0.0-rc.12 +## 06/08/2020 + +1. [](#new) + * Code updates to match Grav 1.7.0-rc.12 +1. [](#improved) + * Changed class `admin-pages` to `admin-{{ target }}` [#59](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/59) + +# v1.0.0-rc.11 +## 05/14/2020 + +1. [](#new) + * Added integration with Admin's new preset events to style the CSS +1. [](#improved) + * JS Maitenance +1. [](#bugfix) + * Fixed `Accounts` Configuration tab + +# v1.0.0-rc.10 +## 04/27/2020 + +1. [](#bugfix) + * Fixed custom actions not working + * Fixed custom folder in `mediapicker` field not working + * Fixed export title when not using CVS [#51](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/51) + * Fixed preview in Page list view [admin#1845](https://github.com/getgrav/grav-plugin-admin/issues/1845) + * Fixed `404 Not Found` error after saving a new object + +# v1.0.0-rc.9 +## 03/20/2020 + +1. [](#bugfix) + * Fixed issue with touch devices and scrollbars hidden, preventing native scrolling to work [admin#1857](https://github.com/getgrav/grav-plugin-admin/issues/1857) [#1858](https://github.com/getgrav/grav-plugin-admin/issues/1858) + + +# v1.0.0-rc.8 +## 03/19/2020 + +1. [](#new) + * Added a basic **Convert Data** CLI Command. Works with `Yaml` <-> `Json` +1. [](#bugfix) + * Fixed jump of the page when applying filters [grav-admin#1830](https://github.com/getgrav/grav-plugin-admin/issues/1830) + * Fixed form resetting when validation fails [grav#2764](https://github.com/getgrav/grav/issues/2764) + +# v1.0.0-rc.7 +## 03/05/2020 + +1. [](#new) + * Added option to change perPage amount of items in Flex List. 'All' also available by only at runtime. +1. [](#improved) + * Page filters now obey admin hide type settings +1. [](#bugfix) + * Fixed fatal error if there is missing blueprint [grav#2834](https://github.com/getgrav/grav/issues/2834) + * Fixed redirect when moving a page [grav#2829](https://github.com/getgrav/grav/issues/2829) + * Fixed no default access set when creating new user from admin [#31](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/31) + * Flex Pages: Fixed page visibility issues when creating a new page [grav#2823](https://github.com/getgrav/grav/issues/2823) + * Flex Pages: Fixed translated page having non-translated status with `system.languages.include_default_lang_file_extension: false` + * Flex Pages: Fixed preview on home page + +# v1.0.0-rc.6 +## 02/11/2020 + +1. [](#new) + * Pass phpstan level 1 tests + * Removed legacy classes for pages, cleanup deprecated Flex types +1. [](#bugfix) + * Fixed call to `grav.flex_objects.getObject()` causing fatal error + * Minor bug fixes + +# v1.0.0-rc.5 +## 02/03/2020 + +1. [](#new) + * No changes, just keeping things in sync with Grav RC version + +# v1.0.0-rc.4 +## 02/03/2020 + +1. [](#new) + * Added support for arbitrary admin menu route for editing a flex type + * Added support for new improved ACL + * Added support for custom layouts by adding `/:layout_name` in url + * Added support for Flex Directory specific Configuration + * Added support for action aliases (`/accounts/configure` instead of `/accounts/users/:configre`) + * Added Flex type `Configuration` + * Enabled `Pages`, `Accounts` and `User Groups` by default + * Stop using deprecated `onAdminRegisterPermissions` event + * Renamed directory `grav-pages` to `pages` + * Renamed directory `grav-accounts` to `user-accounts` + * Renamed directory `grav-user-groups` to `user-groups` +1. [](#improved) + * Flex caching settings were moved into Grav core + * Flex Objects plugin now better integrates to Grav core +1. [](#bugfix) + * Fixed empty directory entries in plugin configuration + * Fixed plugin configuration displaying directories outside of the plugin + * Fixed broken blueprints if there's folder with the name of the blueprint file + * Fixed visible save button when in 404 page + * Fixed missing save location when file does not exist + * Fixed multiple ACL related issues (no access, bad links, information leaks) + * Fixed Admin Panel Page list buttons not appearing in Flex Pages + +# v1.0.0-rc.3 +## 01/02/2020 + +1. [](#new) + * Added root page support for `Flex Pages` +1. [](#bugfix) + * Fixed after save: Edit + * Fixed JS failing on initial filters setup due to no fallback implemented [#2724](https://github.com/getgrav/grav/issues/2724) + +# v1.0.0-rc.2 +## 12/04/2019 + +1. [](#new) + * Admin: Added support for editing `User Groups` + * Admin: `Flex Pages` now support **searching** and **filtering** +1. [](#bugfix) + * Hide hidden/system types (pages, accounts, user groups) from Flex Objects page type [#38](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/38) + +# v1.0.0-rc.1 +## 11/06/2019 + +1. [](#new) + * Added directory configuration option for custom admin templates + * Added `Flex Accounts (Admin)` type to administer user accounts in Flex independently from Grav system setting + * Added `Flex Pages (Admin)` type to administer pages in Flex independently from Grav system setting + * Added blueprint option to hide directory from Flex Objects types page in frontend + * Deprecated all `Flex Page` classes and traits in favor of the new classes in Grav core + * Moved flex object/collection templates to `templates/flex/{TYPE}` which is easier to remember + * Admin: Added support customizable preview and export +1. [](#improved) + * Admin: Allow custom title template when editing object + * Translations: rename MODULAR to MODULE everywhere +1. [](#bugfix) + * Flex Pages: Fixed default language not being translated in both `translatedLanguages()` and `untranslatedLanguages()` results + * Flex Pages: Language interface compatibility fixes + * Flex Pages: Fixed frontend issues with plugin events [#5](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/5) + * Flex Pages: Fixed `filePathClean()` and `filePathClean()` not returning file for folder + * Flex Pages: Fixed multiple multi-language related issues in admin [#10](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/10) + * Flex Pages: Fixed raw edit mode + * File upload is broken for nested fields [#34](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/34) + +# v1.0.0-beta.10 +## 10/03/2019 + +1. [](#bugfix) + * Flex Pages: Fixed moving visible page in admin causing ordering issues [#6](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/6) + * Flex Pages List: Fixed issue where auto-hiding scrollbars in macOS would throw off the dropdown position [#20](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/20) + * Flex Pages: Fixed prev/next page missing pages if pagination was turned on in page header + +# v1.0.0-beta.9 +## 09/26/2019 + +1. [](#improved) + * Show/hide dropdown menu as needed when scrolling the page columns container left and right +1. [](#bugfix) + * PHP 7.1: Fixed error when activating `Flex Pages` in Plugin parameters [#13](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/13) + * Flex Pages: Fixed page template cannot be changed [#4](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/4) + * Flex Pages: Fixed new pages being created with wrong template [#22](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/22) + * Flex Pages: Fixed `Preview` not working [#17](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/17) + * Fixed error caused by automatic path selection from cookie when destination not available [#23](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/23) + * Fixed breadcrumb issue in Flex Pages List [#19](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/19) + * Flex Pages: Fixed unable to change page template [#4](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/4) + * Fixed `Error 404` when adding new contact [#14](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/14) + * Flex Pages: Non-visible items appear in Nav menu [#24](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/24) + * Disabling plugin breaks saving plugin configuration [#11](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/11) + +# v1.0.0-beta.8 +## 09/19/2019 + +1. [](#new) + * Initial public release (all previous versions were in a private repo) diff --git a/plugins/flex-objects/LICENSE b/plugins/flex-objects/LICENSE new file mode 100644 index 0000000..d151da3 --- /dev/null +++ b/plugins/flex-objects/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Trilby Media, LLC + +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/plugins/flex-objects/README.md b/plugins/flex-objects/README.md new file mode 100644 index 0000000..4b91278 --- /dev/null +++ b/plugins/flex-objects/README.md @@ -0,0 +1,289 @@ +# Flex Objects Plugin + +## About + +The **Flex Objects** Plugin is for [Grav CMS](https://github.com/getgrav/grav). Flex objects is a powerful new plugin that allows you to build custom collections of objects, which can modified by CRUD operations via the admin plugin to easily manage large sets of data that don't make sense as simple YAML configuration files, or Grav pages. These objects are defined by blueprints written in YAML and they are rendered by a set of twig files. Additionally both objects and collections can be customized by PHP classes, which allows you to define complex behaviors and relationships between the objects. + +![](assets/flex-objects-list.png) + +![](assets/flex-objects-edit.png) + +![](assets/flex-objects-options.png) + + +## System Requirements + +Plugin requires **Grav** v1.7.19 or later version in order to run. Additionally you need **Form Plugin** v5.1.0 and optionally **Admin Plugin** v1.10.19 or later version. + +## Installation + +Typically a plugin should be installed via [GPM](http://learn.getgrav.org/advanced/grav-gpm) (Grav Package Manager): + +``` +$ bin/gpm install flex-objects +``` + +Alternatively it can be installed via the [Admin Plugin](http://learn.getgrav.org/admin-panel/plugins) + +## Sample Data + +Once installed you can either create entries manually, or you can copy the sample data set: + +```shell +$ mkdir -p user/data/flex-objects +$ cp user/plugins/flex-objects/data/flex-objects/contacts.json user/data/flex-objects/contacts.json +``` + +## Configuration + +This plugin works out of the box, but provides several fields that make modifying and extending this plugin easier: + +```yaml +enabled: true + +built_in_css: true +extra_admin_twig_path: 'theme://admin/templates' +admin_list: + per_page: 15 + order: + by: updated_timestamp + dir: desc + +directories: + - 'blueprints://flex-objects/contacts.yaml' + - 'blueprints://flex-objects/pages.yaml' + - 'blueprints://flex-objects/user-accounts.yaml' + - 'blueprints://flex-objects/user-groups.yaml' +``` + +Simply edit the **Flex Objects** plugin options in the Admin plugin, or copy the `flex-objects.yaml` default file to your `user/config/plugins/` folder and edit the values there. Read below for more help on what these fields do and how they can help you modify the plugin. + +Most interesting configuration option is `directories`, which contains list or blueprint files which will define the flex types. + +## Displaying + +![](assets/flex-objects-site.png) + +just create a page called `flex-objects.md` or set the template of your existing page to `template: flex-objects`. This will use the `flex-objects.html.twig` file provided by the plugin. + + +```twig +--- +title: Directory +flex: + directory: contacts +--- + +# Directory Example +``` + +If you do not specify `flex.directory` name in the page header, the page will list all directories instead of displaying entries from a single directory. + +![](assets/flex-objects-directory.png) + +# Modifications + +This plugin is configured with a sample contacts directory with a few sample fields: + +* published +* first_name +* last_name +* email +* website +* tags + +These are probably not the exact fields you might want, so you will probably want to change them. This is pretty simple to do with Flex Objects, you just need to change the **Blueprints** and the **Twig Templates**. This can be achieved simply enough by copying some current files and modifying them. + +Let's assume you simply want to add a new "Phone Number" field to the existing Data and remove the "Tags". These are the steps you would need to perform: + +1. Copy the `blueprints/flex-objects/contacts.yaml` Blueprint file to another location, let's say `user/blueprints/flex-objects/`. The file can really be stored anywhere, but if you are using admin, it is best to keep the blueprint file where admin can automatically find it. + +!!! **NOTE:** If you want to put the blueprints to `user/themes/yourtheme/blueprints`, you need to use the new blueprint folder structure from Grav 1.7. See [Plugin/Theme Blueprints](https://learn.getgrav.org/17/advanced/grav-development/grav-17-upgrade-guide#plugin-theme-blueprints-blueprints-yaml). + +2. Edit the `user/blueprints/flex-objects/contacts.yaml` like so: + + ```yaml + title: Contacts + description: Simple contact directory with tags. + type: flex-objects + + config: + admin: + list: + title: name + fields: + published: + field: + type: toggle + label: Publ + width: 8 + last_name: + link: edit + first_name: + link: edit + email: + phone: + data: + storage: + class: 'Grav\Framework\Flex\Storage\SimpleStorage' + options: + formatter: + class: 'Grav\Framework\File\Formatter\JsonFormatter' + folder: user-data://flex-objects/contacts.json + + form: + validation: loose + + fields: + published: + type: toggle + label: Published + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + required: true + + last_name: + type: text + label: Last Name + validate: + required: true + + first_name: + type: text + label: First Name + + email: + type: email + label: Email Address + validate: + required: true + + website: + type: url + label: Website URL + + phone: + type: text + label: Phone Number + ``` + + See how we replaced `tags:` with `phone:` in the `config.admin.list.fields` section at the top. Also, notice how we removed the `tags:` Blueprint field definition, and added a simple text field for `phone:`. If you have questions about available form fields, [check out the extensive documentation](https://learn.getgrav.org/forms/blueprints/fields-available) on the subject. + +3. We need to copy the frontend Twig file and modify it to add the new "Phone" field. By default your theme already has its `templates`, so we can take advantage of it 2. We'll simply copy the `user/plugins/flex-objects/templates/flex/contacts/object/default.html.twig` file to `user/themes/quark/templates/flex/contacts/object/default.html.twig`. Notice, there is no reference to `admin/` here, this is site template, not an admin one. We are also assuming you are using `Quark` theme, so you may have to change this to reference the theme you are using. + +4. Edit the `default.html.twig` file you just copied so it has these modifications: + + ```twig +
    + {% if object.website %} + {{ object.last_name }}, {{ object.first_name }} + {% else %} + {{ object.last_name }}, {{ object.first_name }} + {% endif %} + {% if object.email %} +

    + {% endif %} + {% if object.phone %} +

    {{ object.phone }}

    + {% endif %} +
    + ``` + + Notice, we removed the `entry-extra` DIV, and added a new `if` block with the Twig code to display the phone number if set. + +5. We also need to tweak the JavaScript initialization which provides which hooks up certain classes to the search. To do this we need to copy the `user/plugins/flex-objects/templates/flex/contacts/collection/default.html.twig` file to `user/themes/quark/templates/flex/contacts/collection/default.html.twig`. Notice this is the `collection` template this time, not the `object` template as we copied before. + + Edit this file and replace the `` tag at the bottom with this code: + + ```html + + ``` + +# File Upload + +To upload files you can use the `file` form field. [The standard features apply](https://learn.getgrav.org/forms/blueprints/how-to-add-file-upload), and you can simply edit your custom blueprint with a field definition similar to: + +``` + item_image: + type: file + label: Item Image + random_name: true + destination: 'user/data/flex-objects/files' + multiple: true +``` + +> In order to fully take advantage of image uploads, you should always be using `FolderStorage`, meaning that the objects get saved to individual folders together with the images. Other storage layers may or may not support media. + +# Advanced + +You can radically alter the structure of the `contacts.json` data file by making major edits to the `contacts.yaml` blueprint file. However, it's best to start with an empty `contacts.json` if you are making wholesale changes or you will have data conflicts. Best to create your blueprint first. Reloading a **New Entry** until the form looks correct, then try saving, and check to make sure the stored `user/data/flex-objects/contacts.json` file looks correct. + +Then you will need to make more widespread changes to the site Twig templates. You might need to adjust the number of columns and the field names. You will also need to pay attention to the JavaScript initialization in each template. + +# Features + +Here are the main benefits of using Flex objects: + +* CRUD is automatically handled for you by Flex Objects plugin +* Objects can be stored using many different strategies, including single file, file per object or folder per object; using yaml, json etc. +* Flex types can be easily extended by custom PHP collection and object classes +* Both Flex objects and collections know how to render themselves: `echo $object->render($layout, $context)` or `{% render object layout: layout with context %}` +* You can easily create custom layouts for your objects and collections to be used in different pages +* Both Flex objects and collections support serialization and `json_encode()` +* Flex objects support Grav `Medium` objects with few lines of code +* Flex objects can have relations to other Flex objects with few lines of code defining the relation +* Flex directories support indexes which allow searching objects without loading all of them +* Efficient caching for indexes, searches, objects and rendered output + +# Limitations and future improvements + +Right now there are a few limitations: + +* Frontend only has a basic routing for the individual pages (you need to do the advanced routing manually by yourself) +* Administration needs more features like filtering, bulk updates etc +* It would be nice to have an easy way to display Flex admin in other admin plugins (it is already possible, but not easy) +* Optional database storage layer would be nice to have +* We need general collection functions to do simple filtering, like: "display all published items" without custom PHP code + +### Notes: + +1. You can actually use pretty much any folder under the `user/` folder of Grav. Simply edit the **Extra Admin Twig Path** option in the `flex-objects.yaml` file. It defaults to `theme://admin/templates` which means it uses the default theme's `admin/templates/` folder if it exists. +2. You can use any path for front end Twig templates also, if you don't want to put them in your theme, you can add an entry in the **Extra Site Twig Path** option of the `flex-objects.yaml` configuration and point to another location. + +# Tricks and tips + +* You can enable and disable directories from **Plugins** > **Flex Objects** + * New Flex Directories can be registered by simply creating a new blueprint file in `user/blueprints/flex-objects` folder + * You can also add types from your plugins by hooking into `onFlexInit` event (see `AccountsServiceProvider` in Grav) +* To properly create your own custom types, you need at least the object blueprint and the template files for collections and objects +* Use `flex-objects.md` page to create entry point for your own directory + * In page header you can use nested `flex.directory` variable to define the directory (or do it in admin) + * In Admin you can just select the directory under the page title + + +# Parameters supported by Flex page type: + +``` +--- +title: 'Flex Directories' +flex: + directories: + layout: default + list: + - accounts + - contacts +--- +``` + +`directories.layout`: uses template file `templates/flex-objects/directories/[LAYOUT].html.twig` +`directories.list`: list of flex directories displayed in this page diff --git a/plugins/flex-objects/admin/pages/flex-objects.md b/plugins/flex-objects/admin/pages/flex-objects.md new file mode 100644 index 0000000..c9359a2 --- /dev/null +++ b/plugins/flex-objects/admin/pages/flex-objects.md @@ -0,0 +1,7 @@ +--- +title: Flex Objects + +access: + admin.flex-objects: true + admin.super: true +--- \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects.html.twig b/plugins/flex-objects/admin/templates/flex-objects.html.twig new file mode 100644 index 0000000..74ee1ca --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects.html.twig @@ -0,0 +1,30 @@ +{%- set user = admin.user -%} +{%- set route = controller.route -%} +{%- set type = directory.config('admin.template') ?? target -%} + +{# Set action from ?preview=1 #} +{%- if key and uri.currentUri().queryParam('preview') %} + {% set action = 'preview' %} +{% endif -%} + +{%- set template -%} + {%- if action == 'add' -%} + edit + {%- elseif action == 'delete' -%} + list + {%- else -%} + {{- action ?: task ?: 'types' -}} + {%- endif -%} +{%- endset -%} + +{%- set separator = config.system.param_sep -%} +{%- set view_config = directory.config('admin.views.' ~ template) ?? directory.config('admin.' ~ template) ?? [] -%} + +{%- include target ? [ + 'flex-objects/types/' ~ type ~ '/' ~ template ~ '.html.twig', + 'flex-objects/types/default/' ~ template ~ '.html.twig', + 'flex-objects/layouts/404.html.twig' + ] : [ + 'flex-objects/types/default/' ~ template ~ '.html.twig', + 'flex-objects/layouts/404.html.twig' + ] -%} diff --git a/plugins/flex-objects/admin/templates/flex-objects.json.twig b/plugins/flex-objects/admin/templates/flex-objects.json.twig new file mode 100644 index 0000000..c5b85dc --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects.json.twig @@ -0,0 +1 @@ +{{- admin.json_response|json_encode|raw -}} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/layouts/404.html.twig b/plugins/flex-objects/admin/templates/flex-objects/layouts/404.html.twig new file mode 100644 index 0000000..6101c81 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/layouts/404.html.twig @@ -0,0 +1 @@ +{{ 'PLUGIN_FLEX_OBJECTS.ERROR.LAYOUT_NOT_FOUND'|tu(template, null) }} diff --git a/plugins/flex-objects/admin/templates/flex-objects/layouts/accounts/partials/top.html.twig b/plugins/flex-objects/admin/templates/flex-objects/layouts/accounts/partials/top.html.twig new file mode 100644 index 0000000..46b1ef6 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/layouts/accounts/partials/top.html.twig @@ -0,0 +1,25 @@ +{% set active_html = 'class="active"' %} +{% set is_configure = route.gravParam('') is same as('configure') %} +{% set authorize = directory.config('admin.views.configure.authorize') ?? directory.config('admin.configure.authorize') ?? 'admin.super' %} + +{% if allowed %} +
    +
    + {% for name,title in {'user-accounts': 'PLUGIN_ADMIN.USERS', 'user-groups': 'PLUGIN_ADMIN.GROUPS'} %} + {% set current = flex.directory(name) %} + {% if current and current.isAuthorized('list', 'admin', user) %} + {% set active = not is_configure and nav_route|starts_with(flex.adminRoute(current)|trim('/') ~ '/') %} + + {{ title|tu }} + + {% endif %} + {% endfor %} + + {% if user.authorize(authorize) or user.authorize('admin.super') %} + + {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }} + + {% endif %} +
    +
    +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/add.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/add.html.twig new file mode 100644 index 0000000..076f293 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/add.html.twig @@ -0,0 +1,3 @@ + + {{ 'PLUGIN_ADMIN.ADD'|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/back.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/back.html.twig new file mode 100644 index 0000000..dbb57c6 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/back.html.twig @@ -0,0 +1,3 @@ + + {{ "PLUGIN_ADMIN.BACK"|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/configuration.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/configuration.html.twig new file mode 100644 index 0000000..2a463b2 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/configuration.html.twig @@ -0,0 +1,7 @@ +{%- set authorize = directory.config('admin.views.configure.authorize') ?? directory.config('admin.configure.authorize') ?? 'admin.super' %} + +{%- if configure_url and user.authorize(authorize) %} + + {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }} + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/delete.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/delete.html.twig new file mode 100644 index 0000000..779760e --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/delete.html.twig @@ -0,0 +1,3 @@ + + {{ 'PLUGIN_ADMIN.DELETE'|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export-csv.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export-csv.html.twig new file mode 100644 index 0000000..8c53c1b --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export-csv.html.twig @@ -0,0 +1,3 @@ + + {{ 'PLUGIN_FLEX_OBJECTS.CSV'|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export.html.twig new file mode 100644 index 0000000..0f8382b --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/export.html.twig @@ -0,0 +1,21 @@ +{% if export.options %} +
    + + +
    + +{% else %} + + {{ export.title ?? (export.formatter.class ? 'PLUGIN_ADMIN.EXPORT'|tu : 'PLUGIN_FLEX_OBJECTS.CSV'|tu) }} + +{% endif %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/languages.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/languages.html.twig new file mode 100644 index 0000000..58c2551 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/languages.html.twig @@ -0,0 +1,18 @@ +
    + + {% if admin_languages|length > (language in admin_languages)|int %} + + + {% endif %} +
    diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview-open.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview-open.html.twig new file mode 100644 index 0000000..df8893d --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview-open.html.twig @@ -0,0 +1,5 @@ +{% if preview_url %} + + {{ "PLUGIN_ADMIN.OPEN_NEW_TAB"|tu }} + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview.html.twig new file mode 100644 index 0000000..6307913 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/preview.html.twig @@ -0,0 +1,3 @@ + + {{ "PLUGIN_ADMIN.PREVIEW"|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/save.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/save.html.twig new file mode 100644 index 0000000..379d97d --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/buttons/save.html.twig @@ -0,0 +1,4 @@ +{% set task = task ?? 'save' %} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/configure.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/configure.html.twig new file mode 100644 index 0000000..4495527 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/configure.html.twig @@ -0,0 +1,103 @@ +{% extends 'partials/base.html.twig' %} +{% use 'flex-objects/types/default/titlebar/configure.html.twig' %} + +{% set name = view_config['form'] %} +{% set form = form ?? directory.directoryForm(name) %} + +{# Allowed actions #} +{% set can_save = can_save ?? user.authorize(view_config['authorize'] ?? 'admin.super') %} + +{# These variables can be overridden from the main template file #} +{% set allowed = allowed ?? (directory and form and can_save) %} +{% set back_route = back_route ?? ('/' ~ route.getRoute(1)) %} +{% set title_icon = title_icon ?? view_config['icon'] ?? 'fa-cog' %} +{% set title -%} + {%- set title_config = view_config['title'] ?? null -%} + {%- if title_config.template -%} + {{- include(template_from_string(title_config.template, 'configure title template')) -}} + {%- else -%} + {{- directory.title }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu -}} + {% endif %} +{%- endset %} + +{% macro spanToggle(input, length) %} + {{ (repeat('  ', (length - input|length) / 2) ~ input ~ repeat('  ', (length - input|length) / 2))|raw }} +{% endmacro %} +{% import _self as macro %} + +{% block body %} + {% set back_url = back_url ?? admin_route(back_route) %} + + {{ parent() }} +{% endblock body %} + +{% block content_top %} + {% if allowed and user.authorize('admin.super') %} + {% set save_location = directory.getDirectoryConfigUri(name) %} +
    {{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: {{ url(save_location, false, true)|trim('/') }}
    + {% endif %} +{% endblock %} + +{% block topbar %} + {% if user.authorize('admin.super') %} +
    + {% set normalText = 'PLUGIN_ADMIN.NORMAL'|tu %} + {% set expertText = 'PLUGIN_ADMIN.EXPERT'|tu %} + {% set maxLen = max([normalText|length, expertText|length]) %} + {% set normalText = macro.spanToggle(normalText, maxLen) %} + {% set expertText = macro.spanToggle(expertText, maxLen) %} + +
    + + + + + +
    +
    + {% endif %} +{% endblock topbar %} + +{% block content %} + {{ parent() }} + + {% if allowed %} +
    +
    + {# TODO: RAW MODE +
    + {{ block('topbar') }} +
    + #} + {% block edit %} + {% include 'partials/blueprints.html.twig' with { form: form, data: form.data } %} + {% endblock %} +
    +
    + + {% include 'partials/modal-changes-detected.html.twig' %} + + {% else %} + + {% do page.modifyHeader('http_response_code', 404) %} +
    +

    {{ 'PLUGIN_ADMIN.ERROR'|tu }} 404

    +
    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.PAGE_NOT_EXIST'|tu }} +

    +
    +
    + + {% endif %} +{% endblock %} + +{% block bottom %} + {{ parent() }} + + {# TODO: RAW MODE + + #} +{% endblock bottom %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/edit.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/edit.html.twig new file mode 100644 index 0000000..b14310d --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/edit.html.twig @@ -0,0 +1,121 @@ +{% extends 'partials/base.html.twig' %} +{% use 'flex-objects/types/default/titlebar/edit.html.twig' %} + +{# Avoid defining form and object twice: object should always come from the form! #} +{% if form is not defined %} + {% set form = object.form %} + {% set object = form.object %} +{% endif %} + +{# Allowed actions #} +{% set can_list = can_list ?? directory.isAuthorized('list', 'admin', user) %} +{% set can_read = can_read ?? (object.exists ? object.isAuthorized('read', 'admin', user) : directory.isAuthorized('create', 'admin', user)) %} +{% set can_create = can_create ?? object.isAuthorized('create', 'admin', user) %} +{% set can_save = can_save ?? (object.exists ? object.isAuthorized('update', 'admin', user) : directory.isAuthorized('create', 'admin', user)) %} +{% set can_delete = can_delete ?? (object.exists and object.isAuthorized('delete', 'admin', user)) %} +{% set can_translate = can_translate ?? (admin.multilang and object.hasFlexFeature('flex-translate')) %} +{% set can_preview = can_preview ?? (can_read and object.exists and (directory.config('admin.views.preview.enabled') ?? directory.config('admin.preview.enabled', false))) %} + +{# Translations #} +{% if can_translate %} + {% set translate_include_default = translate_include_default ?? grav.config.get('system.languages.include_default_lang_file_extension', true) %} + {% set all_languages = grav.admin.siteLanguages %} + {% set admin_languages = admin.languages_enabled %} + {% set default_language = grav.language.default %} + {% set object_language = object.language %} + {% set language = controller.language %} + {% set has_translation = object.hasTranslation(language, false) %} + + {# + {% if translate_include_default %} + {% set all_languages = all_languages|merge({'': 'Default'}) %} + {% set admin_languages = admin_languages|merge({'': ''}) %} + {% set object_languages = object.languages(true) %} + {% else %} + #} + {% set language = language ?: default_language %} + {% set object_language = object_language ?: default_language %} + {% set object_languages = object.languages(false) %} + {# endif #} +{% endif %} + +{# These variables can be overridden from the main template file #} +{% set allowed = allowed ?? (directory and (object.exists and (can_read or can_save)) or (action == 'add' and can_read)) %} +{% set back_route = back_route ?? ('/' ~ (action != 'edit' and not key ? route.getRoute(1, not can_list ? -1 : null) : route.getRoute(1, not can_list ? -2 : -1))) %} +{% set title_icon = title_icon ?? view_config['icon'] ?? directory.config.admin.menu.list.icon ?? 'fa-file-text-o' %} +{% set title -%} + {%- set title_config = view_config['title'] -%} + {%- if title_config.template -%} + {{- include(template_from_string(title_config.template, 'edit title template')) -}} + {%- else -%} + {{- title ?? object.form.getValue('title') ?? object.title ?? key -}} + {% endif %} +{%- endset %} + +{% block body %} + {% set back_url = back_url ?? admin_route(back_route) %} + {% set id = key %} + {% set blueprint = blueprint ?? form.blueprint %} + + {{ parent() }} +{% endblock body %} + +{% block content_top %} + {% if allowed and user.authorize('admin.super') %} + {% if directory and object or action == 'add' %} + {% set save_location = object.getStorageFolder() ?? directory.getStorageFolder() %} +
    {{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: {{ url(save_location, false, true)|trim('/') }} {{ not object.exists ? '[NEW]' }}
    + {% endif %} + {% endif %} + {% if object.exists and form.flash.exists %} +
    + {{ 'PLUGIN_FLEX_OBJECTS.STATE.EDITING_DRAFT'|tu }} +
    + {% endif %} +{% endblock %} + +{% block content %} + {% if allowed %} +
    +
    +
    + {% block topbar %}{% endblock %} +
    + {% block edit %} + {% include 'partials/blueprints.html.twig' with { form: form, context: object, data: object } %} + {% endblock %} +
    +
    + + {% include 'partials/modal-changes-detected.html.twig' %} + + {% if can_delete %} + {% include ['flex-objects/types/' ~ target ~ '/modals/remove.html.twig', 'flex-objects/types/default/modals/remove.html.twig'] with { name: target } %} + {% endif %} + + {% elseif (object.exists) %} + + {% do page.modifyHeader('http_response_code', 403) %} +
    +

    {{ 'PLUGIN_ADMIN.ERROR'|tu }} 403

    +
    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.PAGE_FORBIDDEN'|tu }} +

    +
    +
    + + {% else %} + + {% do page.modifyHeader('http_response_code', 404) %} +
    +

    {{ 'PLUGIN_ADMIN.ERROR'|tu }} 404

    +
    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.PAGE_NOT_EXIST'|tu }} +

    +
    +
    + + {% endif %} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/list.html.twig new file mode 100644 index 0000000..3da6805 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/list.html.twig @@ -0,0 +1,98 @@ +{% extends 'partials/base.html.twig' %} +{% use 'flex-objects/types/default/titlebar/list.html.twig' %} + +{# Allowed actions #} +{% set export = directory.config('admin.views.export') ?? directory.config('admin.export') ?? [] %} +{% set can_export = can_export ?? (export['enabled'] ?? export|array|count)|bool %} +{% set can_create = can_create ?? directory.isAuthorized('create', 'admin', user) %} +{% set can_translate = can_translate ?? (admin.multilang and directory.object.hasFlexFeature('flex-translate')) %} + +{% set per_page = per_page ?? grav.uri.currentUri.queryParam('per_page') %} + +{# Translations #} +{% if can_translate %} + {% set translate_include_default = translate_include_default ?? grav.config.get('system.languages.include_default_lang_file_extension', true) %} + {% set all_languages = grav.admin.siteLanguages %} + {% set admin_languages = admin.languages_enabled %} + {% set default_language = grav.language.default %} + {% set language = controller.language %} + {# + {% if translate_include_default %} + {% set all_languages = all_languages|merge({'': 'Default'}) %} + {% set admin_languages = admin_languages|merge({'': ''}) %} + {% else %} + #} + {% set language = language ?: default_language %} + {# endif #} +{% endif %} + +{# These variables can be overridden from the main template file #} +{% set allowed = allowed ?? (directory and directory.isAuthorized('list', 'admin', user)) %} +{% set back_route = back_route ?? route.getRoute(1, -1) %} + +{% set configure_path = directory.config('admin.router.actions.configure.path') %} +{% set configure_route = configure_route ?? (configure_path ? route.withRoute(admin_route(configure_path)|trim('/')) : null) %} +{% set configure_route = configure_route ?? route.withGravParam('', 'configure') %} + +{% set title_icon = title_icon ?? view_config['icon'] ?? directory.config.admin.menu.list.icon ?? 'fa-file-text-o' %} +{% set title -%} + {%- set title_config = view_config['title'] ?? null -%} + {%- if title_config.template -%} + {{- include(template_from_string(title_config.template, 'configure title template')) -}} + {%- else -%} + {{- directory.title -}} + {% endif %} +{%- endset %} + +{% set schema = directory.blueprint.schema %} + +{% do assets.addJs('plugin://flex-objects/js/flex-objects.js', { 'group': 'bottom', 'loading': 'defer' }) %} + +{% block body %} + {% set collection = directory ? collection.isAuthorized('list', 'admin', user) %} + {% set directory_config = view_config['options'] ?? config.get('plugins.flex-objects.admin_list', { per_page: 15, order: { by: 'updated_timestamp', dir: 'desc' }}) %} + {% set per_page = max(1, per_page ?? directory_config.per_page) %} + {% set table = directory ? flex.dataTable(collection.flexDirectory(), { collection: collection, limit: per_page, sort: directory_config.order.by ~ '|' ~ directory_config.order.dir }) %} + {% set back_url = admin_route(back_route) %} + {% set configure_url = (directory.config('admin.views.configure.hidden') ?? directory.config('admin.configure.hidden', false)) is not same as(true) ? configure_route.toString(true) %} + + {% set fields = table.getColumns() %} + {% set fields_count = fields ? count(fields) : 0 %} + {% set fields_width = 8 %} + {% set fields_set = 0 %} + {% set title_field = view_config['title'] %} + {% for key,options in fields %} + {% set fields_width = fields_width + options.width ?: 0 %} + {% set fields_set = fields_set + (options.width ? 1 : 0) %} + {% if not title_field and options.link == 'edit' %} + {% set title_field = key %} + {% endif %} + {% endfor %} + + {{ parent() }} +{% endblock body %} + +{% block content_top %} +{% if allowed and user.authorize('admin.super') %} + {% set save_location = directory.getStorageFolder() %} +
    {{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: {{ url(save_location, false, true)|trim('/') }}
    +{% endif %} +{% endblock %} + +{% block content %} +{% if allowed %} + {% block content_list %} + {% include ['flex-objects/types/' ~ target ~ '/list/list.html.twig', 'flex-objects/types/default/list/list.html.twig'] %} + {% endblock %} +{% else %} + {% do page.modifyHeader('http_response_code', 404) %} +
    +

    {{ 'PLUGIN_ADMIN.ERROR'|tu }} 404

    +
    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.PAGE_NOT_EXIST'|tu }} +

    +
    +
    +{% endif %} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list.html.twig new file mode 100644 index 0000000..4f90ad9 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list.html.twig @@ -0,0 +1,112 @@ +{% block directory %} +
    + {% if not fields %} + {% block no_list %} +
    +

    {{ 'PLUGIN_FLEX_OBJECTS.ERROR.BLUEPRINT_NO_LIST'|tu( target, null )|raw }}

    +
      +
    • + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.BLUEPRINT_NO_LIST_ADVISE'|tu }} +
    • +
    • + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.BLUEPRINT_NO_LIST_TEMPLATE'|tu( target, null )|raw }} +
    • +
    +
    + {% endblock %} + {% elseif not collection.count %} + {% block no_entries %} +
    + {% if directory.isAuthorized('create', 'admin', user) %} + {% set createLink = admin_route(flex.adminRoute(collection, {action: 'add'})) %} + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.LIST_EMPTY_ADD'|tu(createLink, null)|raw }} + {% else %} + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.LIST_EMPTY'|tu }} + {% endif %} +
    + {% endblock %} + {% else %} + {% block entries %} + {% set per_page = per_page ?? directory_config.per_page %} + + {% set tableFields = [] %} + {% set searchFields = [] %} + {% for key, options in fields %} + {% set name = key %} + {% set sortField = options.sort.field ?? key %} + {% set title = (options.field.label ?? schema.get(options.alias ?? key).label)|tu %} + {% set width = options.width ?: ((100-fields_width) / ((fields_count-fields_set) ?: 1))|round(3) %} + {% set title_class = options.title_class ?: '' %} + {% set data_class = options.data_class ?: '' %} + + {# Vuetable doesn't like field names with `.` in them, so we convert name and sortField to `_` #} + {% set tableFields = tableFields|merge([ + { + name: name|replace({'.': '_'}), + sortField: sortField, + title: title ?? 'N/A', + width: width ~ '%', + titleClass: title_class, + dataClass: data_class + } + ]) %} + + {# FIXME: Search fields should be passed and individually customizable, right now defaulting to all fields selected #} + {% set searchFields = searchFields|merge([key|replace({'.': '_'})]) %} + {% endfor %} + {% set tableFields = tableFields|merge([{ name: '_actions_', title: 'Actions', titleClass: 'right' }]) %} + + + {% set list = table.jsonSerialize %} + +
    + + {% for i in 0..((min(per_page, list.data|count) + 3) - 1) %} + + + + + + + + + + + + + + + + + + + + + + + + {% endfor %} + +
    + {% endblock %} + {% endif %} + + {% block modals %} + {% include 'flex-objects/types/default/modals/remove.html.twig' with { name: target } %} + {% endblock %} +
    +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list_actions.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list_actions.html.twig new file mode 100644 index 0000000..e4d9da5 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/list/list_actions.html.twig @@ -0,0 +1,39 @@ +{% set object_title = title_field ? "'" ~ object[title_field]|join(' ') ~ "'" : 'Item' %} +{% set can_read = object.isAuthorized('read', 'admin', user) %} +{% set can_update = object.isAuthorized('update', 'admin', user) %} +{% set can_delete = object.isAuthorized('delete', 'admin', user) %} + +{% if can_read and object.getRoute() %} + {% block action_preview %} + + + + {% endblock %} +{% endif %} + +{% if can_update %} + {% block action_edit %} + + + + {% endblock %} +{% elseif can_read %} + {% block action_read %} + + + + {% endblock %} +{% endif %} + +{% if can_delete %} + {% block action_delete %} + + + + {% endblock %} +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/modals/remove.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/modals/remove.html.twig new file mode 100644 index 0000000..f37500a --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/modals/remove.html.twig @@ -0,0 +1,13 @@ +
    +
    + {# FIXME -name|singularize- is not translatable #} +

    {{ 'PLUGIN_FLEX_OBJECTS.ACTION.DELETE_N'|tu }} {{ name|singularize|capitalize }}

    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ACTION.REALLY_DELETE'|tu( name|singularize, null ) }} +

    +
    + + {{ "PLUGIN_ADMIN.CONTINUE"|tu }} +
    +
    +
    diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/preview.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/preview.html.twig new file mode 100644 index 0000000..3e24744 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/preview.html.twig @@ -0,0 +1,62 @@ +{% extends 'partials/base.html.twig' %} +{% use 'flex-objects/types/default/titlebar/preview.html.twig' %} + +{# Allowed actions #} +{% set can_preview = can_preview ?? (object.exists and (view_config['enabled'] ?? false)) %} +{% set can_translate = can_translate ?? (admin.multilang and object.hasFlexFeature('flex-translate')) %} + +{# These variables can be overridden from the main template file #} +{% set allowed = allowed ?? (directory and (object.exists or action == 'add')) %} +{% set back_route = back_route ?? ('/' ~ route.getRoute(1)) %} +{% set title_icon = title_icon ?? view_config['icon'] ?? directory.config.admin.menu.list.icon ?? 'fa-file-text-o' %} +{% set title -%} + {%- set title_config = view_config['title'] -%} + {%- if title_config.template -%} + {{- include(template_from_string(title_config.template, 'edit title template')) -}} + {%- else -%} + {{- title ?? object.form.getValue('title') ?? object.title ?? key -}} + {% endif %} +{%- endset %} +{% set preview_url -%} + {%- set route_config = view_config['route'] -%} + {%- if route_config.template -%} + {{- include(template_from_string(route_config.template, 'preview route template')) -}} + {%- else -%} + {{- preview_url ?? object.getRoute().uri ?: '' -}} + {%- endif -%} +{% endset -%} + +{% block body %} + {% if not can_preview or not preview_url %} + {% set allowed = false %} + {% endif %} + {% set id = key %} + {% set blueprint = object.blueprint ?? directory.blueprint %} + {% set back_url = back_url ?? admin_route(back_route) %} + + {{ parent() }} +{% endblock body %} + +{% block content_wrapper %} +{% if can_preview and allowed and preview_url %} +
    +
    + +
    +
    +{% else %} + {{ parent() }} +{% endif %} +{% endblock content_wrapper %} + +{% block content %} + {% do page.modifyHeader('http_response_code', 404) %} +
    +

    {{ 'PLUGIN_ADMIN.ERROR'|tu }} 404

    +
    +

    + {{ 'PLUGIN_FLEX_OBJECTS.ERROR.PAGE_NOT_EXIST'|tu }} +

    +
    +
    +{% endblock content %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/configure.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/configure.html.twig new file mode 100644 index 0000000..08d6f86 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/configure.html.twig @@ -0,0 +1,32 @@ +{% block titlebar %} + {% block titlebar_button_bar %} +
    + {# BACK #} + {% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/default/buttons/back.html.twig'] %} + {% endblock back_button %} + + {% block extra_buttons %}{% endblock extra_buttons %} + + {# SAVE #} + {% if can_save %} + {% block save_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/save.html.twig', 'flex-objects/types/default/buttons/save.html.twig'] with {task: 'configure'} %} + {% endblock save_button %} + {% endif %} +
    + {% endblock titlebar_button_bar %} + + {% block titlebar_title %} +

    + {% if allowed %} + + {{ title }} + {% else %} + + {{ 'PLUGIN_ADMIN.ERROR'|tu }} +   {% endif %} +

    + {% endblock titlebar_title %} + +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/edit.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/edit.html.twig new file mode 100644 index 0000000..c0b0de6 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/edit.html.twig @@ -0,0 +1,46 @@ +{% block titlebar %} + {% block titlebar_button_bar %} +
    + {# BACK #} + {% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/default/buttons/back.html.twig'] %} + {% endblock back_button %} + + {# PREVIEW #} + {% if can_preview %} + {% block preview_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/preview.html.twig', 'flex-objects/types/default/buttons/preview.html.twig'] %} + {% endblock preview_button %} + {% endif %} + + {# DELETE #} + {% if can_delete %} + {% block delete_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/delete.html.twig', 'flex-objects/types/default/buttons/delete.html.twig'] %} + {% endblock delete_button %} + {% endif %} + + {% block extra_buttons %}{% endblock extra_buttons %} + + {# SAVE #} + {% if allowed and can_save %} + {% block save_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/save.html.twig', 'flex-objects/types/default/buttons/save.html.twig'] with {task: 'save'} %} + {% endblock save_button %} + {% endif %} +
    + {% endblock titlebar_button_bar %} + + {% block titlebar_title %} +

    + {% if allowed %} + + {{ not object.exists ? '[NEW]' }} {{ title }} + {% else %} + + {{ 'PLUGIN_ADMIN.ERROR'|tu }} + {% endif %} +

    + {% endblock titlebar_title %} + +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/list.html.twig new file mode 100644 index 0000000..03a0188 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/list.html.twig @@ -0,0 +1,48 @@ +{% block titlebar %} + {% block titlebar_button_bar %} +
    + {# BACK #} + {% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/default/buttons/back.html.twig'] %} + {% endblock back_button %} + + {# EXPORT #} + {% if can_export %} + {% block export_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/export.html.twig', 'flex-objects/types/default/buttons/export.html.twig'] with {export: directory.config('admin.views.export') ?? directory.config('admin.export') ?? []} %} + {% endblock export_button %} + {% endif %} + + {# CREATE #} + {% if can_create %} + {% block create_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/add.html.twig', 'flex-objects/types/default/buttons/add.html.twig'] %} + {% endblock create_button %} + {% endif %} + + {# LANGUAGES #} + {% if can_translate %} + {% block languages_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/languages.html.twig', 'flex-objects/types/default/buttons/languages.html.twig'] %} + {% endblock languages_button %} + {% endif %} + + {# CONFIGURE #} + {% block configure %} + {% include 'flex-objects/types/default/buttons/configuration.html.twig' %} + {% endblock configure %} +
    + {% endblock titlebar_button_bar %} + + {% block titlebar_title %} +

    + {% if allowed %} + + {{ directory ? title|tu : 'Error' }} + {% else %} + + {{ 'PLUGIN_ADMIN.ERROR'|tu }} +  {% endif %} +

    + {% endblock titlebar_title %} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/preview.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/preview.html.twig new file mode 100644 index 0000000..d50cbe7 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/preview.html.twig @@ -0,0 +1,30 @@ +{% block titlebar %} + {% block titlebar_button_bar %} +
    + {# BACK #} + {% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/default/buttons/back.html.twig'] %} + {% endblock back_button %} + + {# PREVIEW #} + {% if can_preview %} + {% block preview_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/preview-open.html.twig', 'flex-objects/types/default/buttons/preview-open.html.twig'] %} + {% endblock preview_button %} + {% endif %} +
    + {% endblock titlebar_button_bar %} + + {% block titlebar_title %} +

    + {% if allowed %} + + {{ "PLUGIN_ADMIN.PREVIEW"|tu }}: {{ not object.exists ? '[NEW]' }} {{ title }} + {% else %} + + {{ 'PLUGIN_ADMIN.ERROR'|tu }} + {% endif %} +

    + {% endblock titlebar_title %} + +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/types.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/types.html.twig new file mode 100644 index 0000000..97789de --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/titlebar/types.html.twig @@ -0,0 +1,22 @@ +{% block titlebar %} + {% block titlebar_button_bar %} +
    + {# BACK #} + {% block back_button %} + {% include 'flex-objects/types/default/buttons/back.html.twig' %} + {% endblock back_button %} + + {# CONFIGURE #} + {% block configure %} + {% include 'flex-objects/types/default/buttons/configuration.html.twig' %} + {% endblock configure %} +
    + {% endblock titlebar_button_bar %} + + {% block titlebar_title %} +

    + + {{ "PLUGIN_FLEX_OBJECTS.TITLE"|tu }} +

    + {% endblock titlebar_title %} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/default/types.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/default/types.html.twig new file mode 100644 index 0000000..88b1ab2 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/default/types.html.twig @@ -0,0 +1,46 @@ +{% extends 'partials/base.html.twig' %} +{% use 'flex-objects/types/default/titlebar/types.html.twig' %} + +{% set flex = grav['flex_objects'] %} + +{# These variables can be overridden from the main template file #} +{% set back_route = back_route ?? ('/' ~ route.getRoute(1, -1)) %} +{% set configure_route = '/plugins/flex-objects' %} + +{% block body %} + {% set back_url = admin_route(back_route) %} + {% set configure_url = configure_route ? admin_route(configure_route) : null %} + + {{ parent() }} +{% endblock body %} + +{% block content %} + +

    {{ 'PLUGIN_FLEX_OBJECTS.TYPES_TITLE'|tu }}

    + +
    + {% for name,directory in flex.directories if directory.enabled and directory.config('admin.hidden', false) is not same as(true) and not directory.config('admin.menu') %} + {% try %} + {% set collection = directory.collection %} + {% if flex.adminRoute(collection) %} +
    + +

    {{ directory.title|tu }} {{ collection.isAuthorized('list', 'admin', user).count }}

    +

    + {{ directory.description }} +

    +
    + {% endif %} + {% catch %} +
    +

    {{ 'PLUGIN_FLEX_OBJECTS.ERROR.BAD_DIRECTORY'|tu }} '{{ name }}'

    +

    + {{ e.message }} +

    +
    + {% endcatch %} + {% endfor %} + +
    + +{% endblock %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/add.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/add.html.twig new file mode 100644 index 0000000..74242aa --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/add.html.twig @@ -0,0 +1,20 @@ +
    + + + +
    diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/back.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/back.html.twig new file mode 100644 index 0000000..df0761a --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/back.html.twig @@ -0,0 +1,3 @@ + + + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/copy.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/copy.html.twig new file mode 100644 index 0000000..47ec501 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/copy.html.twig @@ -0,0 +1,4 @@ +{# href="{{ uri.addNonce(route.withoutParams().withGravParam('task', 'copy').getUri(), 'admin-form', 'admin-nonce') }}" #} + + {{ "PLUGIN_ADMIN.COPY"|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/delete.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/delete.html.twig new file mode 100644 index 0000000..859b38b --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/delete.html.twig @@ -0,0 +1,3 @@ + + {{ "PLUGIN_ADMIN.DELETE"|tu }} + diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/move.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/move.html.twig new file mode 100644 index 0000000..17f607c --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/move.html.twig @@ -0,0 +1,6 @@ + + {{ "PLUGIN_ADMIN.MOVE"|tu }} + +
    + {% include 'partials/page-move.html.twig' with { blueprints: admin.blueprints('admin/pages/move'), data: context } %} +
    \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-child.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-child.html.twig new file mode 100644 index 0000000..778ccac --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-child.html.twig @@ -0,0 +1,9 @@ +{% if child_url %} + + + +{% else %} + + + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-next.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-next.html.twig new file mode 100644 index 0000000..c38113a --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-next.html.twig @@ -0,0 +1,9 @@ +{% if next_url %} + + + +{% else %} + + + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-parent.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-parent.html.twig new file mode 100644 index 0000000..b57f851 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-parent.html.twig @@ -0,0 +1,9 @@ +{% if parent_url %} + + + +{% else %} + + + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-prev.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-prev.html.twig new file mode 100644 index 0000000..141e9b2 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/nav-prev.html.twig @@ -0,0 +1,9 @@ +{% if prev_url %} + + + +{% else %} + + + +{% endif %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/preview.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/preview.html.twig new file mode 100644 index 0000000..7445322 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/preview.html.twig @@ -0,0 +1,5 @@ +{% if object.routable and object.published %} + + + +{% endif %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/save.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/save.html.twig new file mode 100644 index 0000000..fd401bc --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/buttons/save.html.twig @@ -0,0 +1,23 @@ +{% set task = task ?? 'save' %} +
    + + {% if can_translate %} + {% set untranslated = admin_languages|array_diff(object_languages|merge([language])) %} + {% if count(untranslated) %} + + + {% endif %} + {% endif %} +
    diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/edit.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/edit.html.twig new file mode 100644 index 0000000..baf5c87 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/edit.html.twig @@ -0,0 +1,236 @@ +{% extends 'flex-objects/types/default/edit.html.twig' %} + +{# Avoid defining form and object twice: object should always come from the form! #} +{% set expert = user.authorize('admin.super') and admin.session.expert != '0' %} +{% if expert or form is not defined %} + {% set form = object.form(expert ? 'raw' : '') %} + {% set object = form.object %} +{% endif %} + +{% set title = title ?? form.getValue('header.title') ?? object.title ?? key %} +{% set parent = object.parent %} +{% set can_read = can_read ?? (object.exists ? object.isAuthorized('read', 'admin', user) : object.isAuthorized('create', 'admin', user))|bool %} +{% set can_copy = can_copy ?? (parent.exists and parent.isAuthorized('create', 'admin', user)) %} +{% set can_create = can_create ?? (object.exists and object.isAuthorized('create', 'admin', user)) %} +{% set can_save = can_save ?? (object.exists ? object.isAuthorized('update', 'admin', user) : object.isAuthorized('create', 'admin', user))|bool %} +{% set can_move = can_move ?? can_save and form.blueprint.schema.property('route').type is same as('parents') %} +{% set can_translate = can_translate ?? (admin.multilang and object.hasFlexFeature('page-translate') and not object.root()) %} + +{% macro spanToggle(input, length) %} + {{ (repeat('  ', (length - input|length) / 2) ~ input ~ repeat('  ', (length - input|length) / 2))|raw }} +{% endmacro %} +{% import _self as macro %} + +{% block body %} + {% set current_route = '/' ~ route.getRoute(1) %} + + {% if not object.root() %} + {% set child = object.children.first %} + {% set prev = object.prevSibling %} + {% set next = object.nextSibling %} + + {% set parent_url = parent and not parent.root ? admin_route(back_route) %} + {% set child_url = can_read and child ? admin_route(current_route ~ '/' ~ child.slug) %} + {% set prev_url = can_read and prev ? admin_route(back_route ~ '/' ~ prev.slug) %} + {% set next_url = can_read and next ? admin_route(back_route ~ '/' ~ next.slug) %} + {% endif %} + {% set back_url = back_url ?? admin_route(flex.adminRoute(directory.getFlexType())) %} + + {{ parent() }} +{% endblock body %} + +{% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/pages/buttons/back.html.twig'] + with { back_url: back_url } %} + {% if not object.root() %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/nav-prev.html.twig', 'flex-objects/types/pages/buttons/nav-prev.html.twig'] + with { prev_url: prev_url, title: prev.route } %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/nav-parent.html.twig', 'flex-objects/types/pages/buttons/nav-parent.html.twig'] + with { parent_url: parent_url, title: parent.route } %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/nav-child.html.twig', 'flex-objects/types/pages/buttons/nav-child.html.twig'] + with { child_url: child_url, title: child.route } %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/nav-next.html.twig', 'flex-objects/types/pages/buttons/nav-next.html.twig'] + with { next_url: next_url, title: next.route } %} + {% endif %} +{% endblock back_button %} + +{% block preview_button %} + {% if object.exists and not object.root() %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/preview.html.twig', 'flex-objects/types/pages/buttons/preview.html.twig'] %} + {% endif %} +{% endblock preview_button %} + +{% block delete_button %} + {# FIXME: add support for deleting root file only #} + {% if not object.root() %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/delete.html.twig', 'flex-objects/types/pages/buttons/delete.html.twig'] %} + {% endif %} +{% endblock delete_button %} + +{% block extra_buttons %} + {% if object.exists and not object.root() %} + {% if can_create %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/add.html.twig', 'flex-objects/types/pages/buttons/add.html.twig'] %} + {% endif %} + {% if can_copy %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/copy.html.twig', 'flex-objects/types/pages/buttons/copy.html.twig'] %} + {% endif %} + {% if can_move %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/move.html.twig', 'flex-objects/types/pages/buttons/move.html.twig'] %} + {% endif %} + {% endif %} +{% endblock extra_buttons %} + +{% block save_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/save.html.twig', 'flex-objects/types/pages/buttons/save.html.twig'] %} +{% endblock save_button %} + +{% block content_top %} + {% if allowed and user.authorize('admin.super') %} +
    + {% set save_location = object.getStorageFolder() ?: directory.getStorageFolder() %} + {{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: {{ url(save_location, false, true)|trim('/') }} {{ not object.exists ? '[NEW]' }} (type: {{ (form.getValue('name') ?: 'default') }}) +
    + {% endif %} + {% if object.exists and form.flash.exists %} +
    + {{ 'PLUGIN_FLEX_OBJECTS.STATE.EDITING_DRAFT'|tu }} +
    + {% endif %} + {% if not object.exists %} +
    + {{ 'PLUGIN_FLEX_OBJECTS.STATE.NOT_CREATED_YET'|tu }} +
    + {% elseif can_translate %} + {% set is_default = language is same as(default_language) %} + {% if is_default and default_language in object_languages %} + {% if not translate_include_default and object.property('lang') %} + {# Handle default language extension #} +
    + {% set overrideLanguage = all_languages[object_language] ?? object_language %} + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.USING_OVERRIDE'|tu(overrideLanguage, null)|raw }} + {{ object.hasTranslation('', false) ? 'PLUGIN_FLEX_OBJECTS.LANGUAGE.UNUSED_DEFAULT'|tu|raw }} +
    + {% elseif translate_include_default %} + {% if not object.property('lang') %} +
    + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.USING_DEFAULT'|tu|raw }} +
    + {% elseif object.hasTranslation('', false) %} +
    + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.UNUSED_DEFAULT'|tu|raw }} +
    + {% endif %} + {% endif %} + {% elseif not has_translation %} +
    + {% set overrideLanguage = all_languages[language] ?? object_language %} + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.NOT_TRANSLATED_YET'|tu(overrideLanguage, null)|raw }} + {% if language == object_language %} + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.NO_FALLBACK_FOUND'|tu|raw }} + {% else %} + {% set overrideLanguage = all_languages[object_language] ?? object_language %} + {{ 'PLUGIN_FLEX_OBJECTS.LANGUAGE.FALLING_BACK'|tu(overrideLanguage, null)|raw }} + {% endif %} +
    + {% endif %} + {% endif %} +{% endblock content_top %} + +{% block topbar %} + {% if can_translate %} +
    + + {% if count(object_languages) > (object_language in object_languages)|int %} + + + {% endif %} +
    + {% endif %} + + {% if user.authorize('admin.super') %} +
    + {% set normalText = 'PLUGIN_ADMIN.NORMAL'|tu %} + {% set expertText = 'PLUGIN_ADMIN.EXPERT'|tu %} + {% set maxLen = max([normalText|length, expertText|length]) %} + {% set normalText = macro.spanToggle(normalText, maxLen) %} + {% set expertText = macro.spanToggle(expertText, maxLen) %} + +
    + + + + + +
    +
    + {% endif %} +{% endblock topbar %} + +{% block edit %} + {% include 'partials/blueprints.html.twig' with { context: object, data: object, blueprints: form.blueprint } %} +{% endblock edit %} + +{% block content %} + {{ parent() }} + + {% include 'partials/modal-changes-detected.html.twig' %} + + {% if object.exists %} + {% set modal_data = data({ + route: '/' ~ object.key, + name: object.header.child_type ?? object.blueprint.child_type ?? 'default' + }) %} + +
    + {% include 'partials/blueprints-new.html.twig' with { form: null, blueprints: admin.blueprints('admin/pages/new'), data: modal_data, form_id: 'new-page' } %} +
    + +
    + {% include 'partials/blueprints-new-folder.html.twig' with { form: null, blueprints: admin.blueprints('admin/pages/new_folder'), data: modal_data, form_id: 'new-folder' } %} +
    + +
    + {% include 'partials/blueprints-new.html.twig' with { form: null, blueprints: admin.blueprints('admin/pages/modular_new'), data: modal_data, form_id: 'new-module' } %} +
    + +
    + {% include 'partials/blueprints-copy.html.twig' with { blueprints: admin.blueprints('admin/pages/copy'), data: data({ title: object.title ~ ' (Copy)', folder: object.slug ~ '-copy' }), form_id: 'copy' } %} +
    + {% endif %} + + {# TODO: regular pages support extra modals from admin config #} + +
    +
    +

    {{ 'PLUGIN_FLEX_OBJECTS.PARENTS'|tu }}

    +
    {{ 'PLUGIN_FLEX_OBJECTS.STATE.LOADING'|tu }}
    +
    + +
    +
    + +{% endblock content %} + +{% block bottom %} + {{ parent() }} + +{% endblock bottom %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list.html.twig new file mode 100644 index 0000000..e4ab2f5 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list.html.twig @@ -0,0 +1,29 @@ +{% extends 'flex-objects/types/default/list.html.twig' %} + +{% set can_create = true %} + +{% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/pages/buttons/back.html.twig'] %} +{% endblock back_button %} + +{% block create_button %} + {% for key, add_modal in config.plugins.admin.add_modals %} + {% if add_modal.show_in|default('bar') == 'bar' %} + {{ add_modal.label|tu }} + {% endif %} + {% endfor %} + + {% include ['flex-objects/types/' ~ target ~ '/buttons/add.html.twig', 'flex-objects/types/pages/buttons/add.html.twig'] %} +{% endblock %} + +{% block content_top %}{% endblock %} + +{% block content_list %} + {% set list_layout = grav.uri.param('layout', 'columns') %} + {% include [ + 'flex-objects/types/' ~ target ~ '/list/' ~ list_layout ~ '.html.twig', + 'flex-objects/types/pages/list/' ~ list_layout ~ '.html.twig', + 'flex-objects/types/' ~ target ~ '/list/list.html.twig', + 'flex-objects/types/pages/list/list.html.twig' + ] %} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/columns.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/columns.html.twig new file mode 100644 index 0000000..082bb61 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/columns.html.twig @@ -0,0 +1,155 @@ +{% macro toggle(id, title, filters, name = null) %} + {% set name = id|fieldName %} + {% set filter = filters[name] ?? null %} + {% set value = filter is null ? 0 : (not filter)|int+1 %} + {% set classes = ['status-unchecked', 'status-checked', 'status-indeterminate'] %} + + + + + +{% endmacro %} + +{% import _self as macros %} + +{% block directory %} + {% set filters = grav.request.getCookieParams()['grav-admin-flexpages']|base64_decode|json_decode(true)['filters'] %} + {% set hidePanel = filters|length == 0 or (filters|length == 1 and filters['filters[search]']) %} +
    +
    +
    + + +
    +
    + + {{ 'PLUGIN_FLEX_OBJECTS.FILTER.PAGE_ATTRIBUTES'|tu }} + + {{ macros.toggle('filters.routable', 'Routable', filters) }} + {{ macros.toggle('filters.module', 'Module', filters) }} + {{ macros.toggle('filters.visible', 'Visible', filters) }} + {{ macros.toggle('filters.published', 'Published', filters) }} + {{ macros.toggle('filters.translated', 'Translated', filters) }} + {{ macros.toggle('filters.folder', 'Empty Folder', filters) }} +
    + + {% set selected = filters['filters[page_type]']|split(',') %} + {% set page_types = admin.types(null) %} {# directory.config('filters.ignore_page_types') #} +
    + + {{ 'PLUGIN_FLEX_OBJECTS.FILTER.PAGE_TYPES'|tu }} + + {% for name,title in page_types %} + + + + + {% endfor %} +
    + + {% set module_types = admin.modularTypes(null) %} {# directory.config('filters.ignore_module_types') #} + {% if module_types %} +
    + + {{ 'PLUGIN_FLEX_OBJECTS.FILTER.MODULAR_TYPES'|tu }} + + {% for name,title in module_types %} + + + + + {% endfor %} +
    + {% endif %} + + + {{ 'PLUGIN_FLEX_OBJECTS.ACTION.APPLY_FILTERS'|tu }} + + + {{ 'PLUGIN_FLEX_OBJECTS.ACTION.RESET_FILTERS'|tu }} + +
    +
    +
    + +
    +
    {{ 'PLUGIN_FLEX_OBJECTS.STATE.LOADING'|tu }}
    +
    +
    + +
    + + {# Modals #} +
    + {% include 'partials/blueprints-new.html.twig' with { blueprints: admin.blueprints('admin/pages/new'), data: obj_data, form_id: 'new-page' } %} +
    + +
    + {% include 'partials/blueprints-new-folder.html.twig' with { blueprints: admin.blueprints('admin/pages/new_folder'), data: obj_data, form_id: 'new-folder' } %} +
    + +
    + {% include 'partials/blueprints-new.html.twig' with { blueprints: admin.blueprints('admin/pages/modular_new'), data: obj_data, form_id: 'new-module' } %} +
    + + {% for key, add_modal in config.plugins.admin.add_modals %} +
    + {% include add_modal.template|defined('partials/blueprints-new.html.twig') with { + blueprints: admin.blueprints(add_modal.blueprint), + data: obj_data, + form_id: 'add-modal' + }|merge(add_modal.with|defined({})) %} +
    + {% endfor %} + +
    + {% include 'partials/blueprints-copy.html.twig' with { blueprints: admin.blueprints('admin/pages/copy'), data: obj_data, form_id: 'copy' } %} +
    + +
    +
    +

    Parents

    +
    +
    {{ 'PLUGIN_FLEX_OBJECTS.STATE.LOADING'|tu }}
    +
    +
    + +
    +
    + +
    +
    +

    {{ "PLUGIN_ADMIN.MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE"|tu }}

    +

    + {% if context %} + {{ "PLUGIN_ADMIN.PAGE"|tu }}: {{ context.title }} + {% endif %} +

    +

    + {{ "PLUGIN_ADMIN.MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC"|tu }} +

    +
    +
    + + {{ "PLUGIN_ADMIN.CONTINUE"|tu }} +
    +
    +
    +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/list.html.twig new file mode 100644 index 0000000..12ac6ad --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/list/list.html.twig @@ -0,0 +1,41 @@ +{% extends 'flex-objects/types/default/list/list.html.twig' %} + +{% block modals %} +
    + {% include 'partials/blueprints-new.html.twig' with { blueprints: admin.blueprints('admin/pages/new'), data: obj_data, form_id: 'new-page' } %} +
    + +
    + {% include 'partials/blueprints-new-folder.html.twig' with { blueprints: admin.blueprints('admin/pages/new_folder'), data: obj_data, form_id: 'new-folder' } %} +
    + +
    + {% include 'partials/blueprints-new.html.twig' with { blueprints: admin.blueprints('admin/pages/modular_new'), data: obj_data, form_id: 'new-module' } %} +
    + + {% for key, add_modal in config.plugins.admin.add_modals %} +
    + {% include add_modal.template|defined('partials/blueprints-new.html.twig') with { + blueprints: admin.blueprints(add_modal.blueprint), + data: obj_data, + form_id: 'add-modal' + }|merge(add_modal.with|defined({})) %} +
    + {% endfor %} + +
    + {% include 'partials/blueprints-copy.html.twig' with { blueprints: admin.blueprints('admin/pages/copy'), data: obj_data, form_id: 'copy' } %} +
    + +
    +
    +

    Parents

    +
    {{ 'PLUGIN_FLEX_OBJECTS.STATE.LOADING'|tu }}
    +
    + +
    +
    +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/pages/preview.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/pages/preview.html.twig new file mode 100644 index 0000000..61499ee --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/pages/preview.html.twig @@ -0,0 +1,16 @@ +{% extends 'flex-objects/types/default/preview.html.twig' %} + +{% set can_translate = can_translate ?? (admin.multilang and object.hasFlexFeature('page-translate')) %} + +{% block back_button %} + {% include ['flex-objects/types/' ~ target ~ '/buttons/back.html.twig', 'flex-objects/types/pages/buttons/back.html.twig'] + with { back_url: back_url } %} +{% endblock back_button %} + +{% block body %} + {% set parent = object.parent %} + + {% set preview_url = preview_url ?: (object.home ? '/' : '') %} + + {{ parent() }} +{% endblock body %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/configure.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/configure.html.twig new file mode 100644 index 0000000..2348417 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/configure.html.twig @@ -0,0 +1,9 @@ +{% extends 'flex-objects/types/default/configure.html.twig' %} + +{% set back_route = back_route ?? ('/' ~ route.getRoute(1, -1)) %} + +{% block content_top %} + {% include 'flex-objects/layouts/accounts/partials/top.html.twig' %} + + {{ parent() }} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/edit.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/edit.html.twig new file mode 100644 index 0000000..85fe5fd --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/edit.html.twig @@ -0,0 +1,9 @@ +{% extends 'flex-objects/types/default/edit.html.twig' %} + +{% if not directory.isAuthorized('list', 'admin', user) %} + {% set back_route = '/' %} +{% endif %} + +{% if not object.exists %} + {% do object.onPrepareRegistration() %} +{% endif %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/list.html.twig new file mode 100644 index 0000000..0cefbb8 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/user-accounts/list.html.twig @@ -0,0 +1,7 @@ +{% extends 'flex-objects/types/default/list.html.twig' %} + +{% block content_top %} + {% include 'flex-objects/layouts/accounts/partials/top.html.twig' %} + + {{ parent() }} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/configure.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/configure.html.twig new file mode 100644 index 0000000..2348417 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/configure.html.twig @@ -0,0 +1,9 @@ +{% extends 'flex-objects/types/default/configure.html.twig' %} + +{% set back_route = back_route ?? ('/' ~ route.getRoute(1, -1)) %} + +{% block content_top %} + {% include 'flex-objects/layouts/accounts/partials/top.html.twig' %} + + {{ parent() }} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/list.html.twig b/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/list.html.twig new file mode 100644 index 0000000..0cefbb8 --- /dev/null +++ b/plugins/flex-objects/admin/templates/flex-objects/types/user-groups/list.html.twig @@ -0,0 +1,7 @@ +{% extends 'flex-objects/types/default/list.html.twig' %} + +{% block content_top %} + {% include 'flex-objects/layouts/accounts/partials/top.html.twig' %} + + {{ parent() }} +{% endblock %} diff --git a/plugins/flex-objects/admin/templates/forms/fields/flex-objects/flex-objects.html.twig b/plugins/flex-objects/admin/templates/forms/fields/flex-objects/flex-objects.html.twig new file mode 100644 index 0000000..92db6dc --- /dev/null +++ b/plugins/flex-objects/admin/templates/forms/fields/flex-objects/flex-objects.html.twig @@ -0,0 +1,69 @@ +{% extends "forms/field.html.twig" %} + +{% macro spanToggle(input, length) %} + {% set space = repeat('  ', (length - input|length) / 2) %} + {{ (space ~ input ~ space)|raw }} +{% endmacro %} + +{% import _self as macro %} + +{% set value = (value is null ? field.default : value) %} + +{% block global_attributes %} + {{ parent() }} + data-grav-field-name="{{ (scope ~ field.name)|fieldName }}" +{% endblock %} + +{% block input %} + {% set flex = grav['flex_objects'] %} + {% set all = flex.blueprints %} + {% if all|count %} + {% set legacy = flex.getLegacyBlueprintMap() %} + {% for label, directory in all %} + {% set url = directory.blueprintFile %} + {% set found = url in value %} + {% if not found and legacy[url] is defined %} + {% set found = legacy[url] in value %} + {% endif %} + +
    +
    + {% set maxLen = 0 %} + {% for text in ['PLUGIN_ADMIN.ENABLED', 'PLUGIN_ADMIN.DISABLED'] %} + {% set translation = grav.twig.twig.filters['tu'] is defined ? text|tu : text|t %} + {% set maxLen = max(translation|length, maxLen) %} + {% endfor %} + + {% set id = "toggle_" ~ field.name ~ '_' ~ label %} + + + {% set text = 'PLUGIN_ADMIN.ENABLED' %} + {% set translation = (grav.twig.twig.filters['tu'] is defined ? text|tu : text|t)|trim %} + + + {% set text = 'PLUGIN_ADMIN.DISABLED' %} + {% set translation = (grav.twig.twig.filters['tu'] is defined ? text|tu : text|t)|trim %} + +
    + {{ directory.title }} +
    + {% endfor %} + {% else %} +
    {{ 'PLUGIN_FLEX_OBJECTS.ERROR.NO_FLEX_DIRECTORIES'|tu }}
    + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/plugins/flex-objects/admin/templates/forms/fields/save-redirect/save-redirect.html.twig b/plugins/flex-objects/admin/templates/forms/fields/save-redirect/save-redirect.html.twig new file mode 100644 index 0000000..2541a3f --- /dev/null +++ b/plugins/flex-objects/admin/templates/forms/fields/save-redirect/save-redirect.html.twig @@ -0,0 +1,37 @@ +{% extends "forms/field.html.twig" %} + +{% set originalValue = value %} +{% set value = (value is null ? field.default : value) %} +{% set isNew = key ? false : true %} +{% set savedOption = grav.session.post_entries_save|default('create-new') %} + +{% if isNew %} + {% set options = {'create-new':'PLUGIN_FLEX_OBJECTS.ACTION.CREATE_NEW', 'edit':'PLUGIN_FLEX_OBJECTS.ACTION.EDIT_ITEM', 'list':'PLUGIN_FLEX_OBJECTS.ACTION.LIST_ITEMS'} %} +{% else %} + {% set options = {'edit':'PLUGIN_FLEX_OBJECTS.ACTION.EDIT_ITEM', 'list':'PLUGIN_FLEX_OBJECTS.ACTION.LIST_ITEMS'} %} +{% endif %} + +{% block input %} + {% set savedOption = not isNew and savedOption == 'create-new' ? 'edit' : savedOption %} + {% for key, text in options %} + {% set id = field.id|default(field.name) ~ '-' ~ key %} + + {% if savedOption == key %} + {% set value = savedOption %} + {% endif %} + + + + + + + + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/plugins/flex-objects/app/columns/finder.js b/plugins/flex-objects/app/columns/finder.js new file mode 100644 index 0000000..ac623b2 --- /dev/null +++ b/plugins/flex-objects/app/columns/finder.js @@ -0,0 +1,425 @@ +import $ from 'jquery'; +import Finder from '../utils/finder'; +import { getInitialRoute, getStore, setInitialRoute } from './index'; +// import getFilters from '../utils/get-filters'; + +let XHRUUID = 0; +const GRAV_CONFIG = typeof global.GravConfig !== 'undefined' ? global.GravConfig : global.GravAdmin.config; + +export const Instances = {}; + +const isInViewport = (elem) => { + const bounding = elem.getBoundingClientRect(); + const titlebar = document.querySelector('#titlebar'); + const offset = titlebar ? titlebar.getBoundingClientRect().height : 0; + return ( + bounding.top >= offset && + bounding.left >= 0 && + bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + bounding.right <= (window.innerWidth || document.documentElement.clientWidth) + ); +}; + +export class FlexPages { + constructor(container, data) { + this.container = $(container); + this.data = data; + const dataLoad = this.dataLoad; + + this.finder = new Finder( + this.container, + (parent, callback) => { + return dataLoad.call(this, parent, callback); + }, + { + labelKey: 'title', + defaultPath: getInitialRoute(), + itemTrigger: '[data-flexpages-expand]', + createItem: function(item) { + return FlexPages.createItem(this.config, item, this); + }, + createItemContent: function(item) { + return FlexPages.createItemContent(this.config, item, this); + } + } + ); + + this.finder.$emitter.on('leaf-selected', (item) => { + setInitialRoute({ + route: item.route.raw + }); + }); + + this.finder.$emitter.on('interior-selected', (item) => { + setInitialRoute({ + route: item.route.raw + }); + }); + + /* + this.finder.$emitter.on('leaf-selected', (item) => { + console.log('selected', item); + this.finder.emit('create-column', () => this.createSimpleColumn(item)); + }); + + this.finder.$emitter.on('item-selected', (selected) => { + console.log('selected', selected); + // for future use only - create column-card creation for file with details like in macOS finder + // this.finder.$emitter('create-column', () => this.createSimpleColumn(selected)); + }); */ + + this.finder.$emitter.on('column-created', () => { + this.container[0].scrollLeft = this.container[0].scrollWidth - this.container[0].clientWidth; + }); + } + + static createItem(config, item, finder) { + const listItem = $('
  • '); + const listItemClasses = [config.className.item]; + // const href = `${GRAV_CONFIG.current_url}/${item.route.raw}`.replace('//', '/'); + const link = $('
    '); + const createItemContent = config.createItemContent || finder.createItemContent; + const fragment = createItemContent.call(this, item); + link.append(fragment) + // .attr('href', href) + .attr('tabindex', -1); + + if (item.url) { + link.attr('href', item.url); + listItemClasses.push(item.className); + } + + if (item[config.childKey]) { + listItemClasses.push(config.className[config.childKey]); + } + + if (item.filters_hit) { + listItemClasses.push('filters-hit'); + } + + listItem.addClass(listItemClasses.join(' ')); + listItem.append(link) + .attr('data-fjs-item', item[config.itemKey]); + + listItem[0]._item = item; + + return listItem; + } + + static createItemContent(config, item) { + const frag = document.createDocumentFragment(); + const route = `${GRAV_CONFIG.current_url}/${item.route.raw}`.replace('//', '/'); + const title = $('
    '); + const link = $(``); + const icon = $(``); + + if (item.extras && item.extras.lang) { + let status = ''; + if (item.extras.translated) { + status = 'translated'; + } + + if (item.extras.lang === 'n/a') { + status = 'not-available'; + } + + const lang = $(`${item.extras.lang}`); + lang.appendTo(icon); + } + + if (item.extras && item.extras && (item.extras.published_date || item.extras.unpublished_date)) { + const clock = $(''); + clock.appendTo(icon); + } + + const info = $(`${item.title} ${item.route.display}`); + const actions = $(''); + + let dotdotdot = null; + if (item.extras) { + const LANG_URL = $('[data-lang-url]').data('langUrl'); + dotdotdot = $('
    '); + dotdotdot.on('click', (event) => { + if (!dotdotdot.find('.dropdown-menu').length) { + let tags = ''; + let langs = ''; + + item.extras.tags.forEach((tag) => { + tags += `${tag}`; + }); + + const translations = item.extras.langs || {}; + Object.keys(translations).forEach((lang) => { + const translated = translations[lang]; + langs += `
    ${lang ? lang : 'default'}`; + }); + + const canPreview = item.extras.actions.includes('preview') && (!(item.extras.tags.includes('non-routable') || item.extras.tags.includes('unpublished'))); + const canEdit = item.extras.actions.includes('edit'); + const canCopy = item.extras.actions.includes('copy'); + const canMove = false; // item.extras.actions.includes('move'); + const canDelete = item.extras.actions.includes('delete'); + const ul = $(``); + ul.appendTo(dotdotdot); + } + + return true; + }); + } + + if (item.child_count) { + const button = $('