Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 231
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
JPCRM_Fonts
0.00% covered (danger)
0.00%
0 / 228
0.00% covered (danger)
0.00%
0 / 17
8372
0.00% covered (danger)
0.00%
0 / 1
 list_all_available
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
90
 zip_to_font_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 font_name_to_zip
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 font_name_to_regular_ttf_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 font_name_to_dir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 font_slug_to_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 font_name_to_slug
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 font_is_available
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 font_is_installed
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 install_font
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
132
 install_default_fonts
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 install_font_family
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
380
 retrieve_and_install_specific_font
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
132
 extract_and_install_default_fonts
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 load_font
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
110
 loaded_fonts
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 default_fonts_installed
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 *
6 * Logic concerned with installing and using different fonts, primarily in the creation of PDF files
7 *
8 */
9
10// Require DOMPDF
11global $zbs;
12$zbs->libLoad( 'dompdf' );
13use FontLib\Font;
14
15defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
16
17/*
18* Class encapsulating logic concerned with installing and using different fonts
19*/
20class JPCRM_Fonts {
21    /*
22    * Returns a list of fonts available via our CDN:
23    *
24    * @param: $cleaned_alphabetical bool - if true return the list with 'Noto' moved to back of string and re-ordered to be alphabetic
25    * ... e.g. 'Noto Kufi Arabic' => 'Kufi Arabic (Noto)'
26    */
27    public function list_all_available( $cleaned_alphabetical = false ) {
28
29        // updated 17/04/24: Noto Sans, and added CJK fonts (JP, HK, TC, KR and SC)
30        $font_json =
31        '{"Boku2.zip":"Boku2","NotoKufiArabic.zip":"Noto Kufi Arabic","NotoLoopedLao.zip":"Noto Looped Lao","NotoLoopedLaoUI.zip":"Noto Looped Lao UI","NotoLoopedThai.zip":"Noto Looped Thai","NotoLoopedThaiUI.zip":"Noto Looped Thai UI","NotoMusic.zip":"Noto Music","NotoNaskhArabic.zip":"Noto Naskh Arabic","NotoNaskhArabicUI.zip":"Noto Naskh Arabic UI","NotoNastaliqUrdu.zip":"Noto Nastaliq Urdu","NotoRashiHebrew.zip":"Noto Rashi Hebrew","NotoSansAdlam.zip":"Noto Sans Adlam","NotoSansAdlamUnjoined.zip":"Noto Sans Adlam Unjoined","NotoSansAnatolianHieroglyphs.zip":"Noto Sans Anatolian Hieroglyphs","NotoSansArabic.zip":"Noto Sans Arabic","NotoSansArabicUI.zip":"Noto Sans Arabic UI","NotoSansArmenian.zip":"Noto Sans Armenian","NotoSansAvestan.zip":"Noto Sans Avestan","NotoSansBalinese.zip":"Noto Sans Balinese","NotoSansBamum.zip":"Noto Sans Bamum","NotoSansBassaVah.zip":"Noto Sans Bassa Vah","NotoSansBatak.zip":"Noto Sans Batak","NotoSansBengali.zip":"Noto Sans Bengali","NotoSansBengaliUI.zip":"Noto Sans Bengali UI","NotoSansBhaiksuki.zip":"Noto Sans Bhaiksuki","NotoSansBrahmi.zip":"Noto Sans Brahmi","NotoSansBuginese.zip":"Noto Sans Buginese","NotoSansBuhid.zip":"Noto Sans Buhid","NotoSansCanadianAboriginal.zip":"Noto Sans Canadian Aboriginal","NotoSansCarian.zip":"Noto Sans Carian","NotoSansCaucasianAlbanian.zip":"Noto Sans Caucasian Albanian","NotoSansChakma.zip":"Noto Sans Chakma","NotoSansCham.zip":"Noto Sans Cham","NotoSansCherokee.zip":"Noto Sans Cherokee","NotoSansCoptic.zip":"Noto Sans Coptic","NotoSansCuneiform.zip":"Noto Sans Cuneiform","NotoSansCypriot.zip":"Noto Sans Cypriot","NotoSansDeseret.zip":"Noto Sans Deseret","NotoSansDevanagari.zip":"Noto Sans Devanagari","NotoSansDevanagariUI.zip":"Noto Sans Devanagari UI","NotoSansDisplay.zip":"Noto Sans Display","NotoSansDuployan.zip":"Noto Sans Duployan","NotoSansEgyptianHieroglyphs.zip":"Noto Sans Egyptian Hieroglyphs","NotoSansElbasan.zip":"Noto Sans Elbasan","NotoSansElymaic.zip":"Noto Sans Elymaic","NotoSansEthiopic.zip":"Noto Sans Ethiopic","NotoSansGeorgian.zip":"Noto Sans Georgian","NotoSansGlagolitic.zip":"Noto Sans Glagolitic","NotoSansGothic.zip":"Noto Sans Gothic","NotoSansGrantha.zip":"Noto Sans Grantha","NotoSansGujarati.zip":"Noto Sans Gujarati","NotoSansGujaratiUI.zip":"Noto Sans Gujarati UI","NotoSansGunjalaGondi.zip":"Noto Sans Gunjala Gondi","NotoSansGurmukhi.zip":"Noto Sans Gurmukhi","NotoSansGurmukhiUI.zip":"Noto Sans Gurmukhi UI","NotoSansHanifiRohingya.zip":"Noto Sans Hanifi Rohingya","NotoSansHanunoo.zip":"Noto Sans Hanunoo","NotoSansHatran.zip":"Noto Sans Hatran","NotoSansHebrew.zip":"Noto Sans Hebrew","NotoSansHongKong.zip":"Noto Sans Hong Kong","NotoSansImperialAramaic.zip":"Noto Sans Imperial Aramaic","NotoSansIndicSiyaqNumbers.zip":"Noto Sans Indic Siyaq Numbers","NotoSansInscriptionalPahlavi.zip":"Noto Sans Inscriptional Pahlavi","NotoSansInscriptionalParthian.zip":"Noto Sans Inscriptional Parthian","NotoSansJapanese.zip":"Noto Sans Japanese","NotoSansJavanese.zip":"Noto Sans Javanese","NotoSansKaithi.zip":"Noto Sans Kaithi","NotoSansKannada.zip":"Noto Sans Kannada","NotoSansKannadaUI.zip":"Noto Sans Kannada UI","NotoSansKayahLi.zip":"Noto Sans Kayah Li","NotoSansKharoshthi.zip":"Noto Sans Kharoshthi","NotoSansKhmer.zip":"Noto Sans Khmer","NotoSansKhmerUI.zip":"Noto Sans Khmer UI","NotoSansKhojki.zip":"Noto Sans Khojki","NotoSansKhudawadi.zip":"Noto Sans Khudawadi","NotoSansKorean.zip":"Noto Sans Korean","NotoSansLao.zip":"Noto Sans Lao","NotoSansLaoUI.zip":"Noto Sans Lao UI","NotoSansLepcha.zip":"Noto Sans Lepcha","NotoSansLimbu.zip":"Noto Sans Limbu","NotoSansLinearA.zip":"Noto Sans Linear A","NotoSansLinearB.zip":"Noto Sans Linear B","NotoSansLisu.zip":"Noto Sans Lisu","NotoSansLycian.zip":"Noto Sans Lycian","NotoSansLydian.zip":"Noto Sans Lydian","NotoSansMahajani.zip":"Noto Sans Mahajani","NotoSansMalayalam.zip":"Noto Sans Malayalam","NotoSansMalayalamUI.zip":"Noto Sans Malayalam UI","NotoSansMandaic.zip":"Noto Sans Mandaic","NotoSansManichaean.zip":"Noto Sans Manichaean","NotoSansMarchen.zip":"Noto Sans Marchen","NotoSansMasaramGondi.zip":"Noto Sans Masaram Gondi","NotoSansMath.zip":"Noto Sans Math","NotoSansMayanNumerals.zip":"Noto Sans Mayan Numerals","NotoSansMedefaidrin.zip":"Noto Sans Medefaidrin","NotoSansMeeteiMayek.zip":"Noto Sans Meetei Mayek","NotoSansMendeKikakui.zip":"Noto Sans Mende Kikakui","NotoSansMeroitic.zip":"Noto Sans Meroitic","NotoSansMiao.zip":"Noto Sans Miao","NotoSansModi.zip":"Noto Sans Modi","NotoSansMongolian.zip":"Noto Sans Mongolian","NotoSansMono.zip":"Noto Sans Mono","NotoSansMro.zip":"Noto Sans Mro","NotoSansMultani.zip":"Noto Sans Multani","NotoSansMyanmar.zip":"Noto Sans Myanmar","NotoSansMyanmarUI.zip":"Noto Sans Myanmar UI","NotoSansNKo.zip":"Noto Sans N Ko","NotoSansNabataean.zip":"Noto Sans Nabataean","NotoSansNewTaiLue.zip":"Noto Sans New Tai Lue","NotoSansNewa.zip":"Noto Sans Newa","NotoSansNushu.zip":"Noto Sans Nushu","NotoSansOgham.zip":"Noto Sans Ogham","NotoSansOlChiki.zip":"Noto Sans Ol Chiki","NotoSansOldHungarian.zip":"Noto Sans Old Hungarian","NotoSansOldItalic.zip":"Noto Sans Old Italic","NotoSansOldNorthArabian.zip":"Noto Sans Old North Arabian","NotoSansOldPermic.zip":"Noto Sans Old Permic","NotoSansOldPersian.zip":"Noto Sans Old Persian","NotoSansOldSogdian.zip":"Noto Sans Old Sogdian","NotoSansOldSouthArabian.zip":"Noto Sans Old South Arabian","NotoSansOldTurkic.zip":"Noto Sans Old Turkic","NotoSansOriya.zip":"Noto Sans Oriya","NotoSansOriyaUI.zip":"Noto Sans Oriya UI","NotoSansOsage.zip":"Noto Sans Osage","NotoSansOsmanya.zip":"Noto Sans Osmanya","NotoSansPahawhHmong.zip":"Noto Sans Pahawh Hmong","NotoSansPalmyrene.zip":"Noto Sans Palmyrene","NotoSansPauCinHau.zip":"Noto Sans Pau Cin Hau","NotoSansPhagsPa.zip":"Noto Sans Phags Pa","NotoSansPhoenician.zip":"Noto Sans Phoenician","NotoSansPsalterPahlavi.zip":"Noto Sans Psalter Pahlavi","NotoSansRejang.zip":"Noto Sans Rejang","NotoSansRunic.zip":"Noto Sans Runic","NotoSansSamaritan.zip":"Noto Sans Samaritan","NotoSansSaurashtra.zip":"Noto Sans Saurashtra","NotoSansSimplifiedChinese.zip":"Noto Sans Simplified Chinese","NotoSansSharada.zip":"Noto Sans Sharada","NotoSansShavian.zip":"Noto Sans Shavian","NotoSansSiddham.zip":"Noto Sans Siddham","NotoSansSignWriting.zip":"Noto Sans Sign Writing","NotoSansSinhala.zip":"Noto Sans Sinhala","NotoSansSinhalaUI.zip":"Noto Sans Sinhala UI","NotoSansSogdian.zip":"Noto Sans Sogdian","NotoSansSoraSompeng.zip":"Noto Sans Sora Sompeng","NotoSansSoyombo.zip":"Noto Sans Soyombo","NotoSansSundanese.zip":"Noto Sans Sundanese","NotoSansSylotiNagri.zip":"Noto Sans Syloti Nagri","NotoSansSymbols.zip":"Noto Sans Symbols","NotoSansSymbols2.zip":"Noto Sans Symbols2","NotoSansSyriac.zip":"Noto Sans Syriac","NotoSansTagalog.zip":"Noto Sans Tagalog","NotoSansTagbanwa.zip":"Noto Sans Tagbanwa","NotoSansTaiLe.zip":"Noto Sans Tai Le","NotoSansTaiTham.zip":"Noto Sans Tai Tham","NotoSansTaiViet.zip":"Noto Sans Tai Viet","NotoSansTakri.zip":"Noto Sans Takri","NotoSansTamil.zip":"Noto Sans Tamil","NotoSansTamilSupplement.zip":"Noto Sans Tamil Supplement","NotoSansTamilUI.zip":"Noto Sans Tamil UI","NotoSansTaiwan.zip":"Noto Sans Taiwan","NotoSansTelugu.zip":"Noto Sans Telugu","NotoSansTeluguUI.zip":"Noto Sans Telugu UI","NotoSansThaana.zip":"Noto Sans Thaana","NotoSansThai.zip":"Noto Sans Thai","NotoSansThaiUI.zip":"Noto Sans Thai UI","NotoSansTifinagh.zip":"Noto Sans Tifinagh","NotoSansTirhuta.zip":"Noto Sans Tirhuta","NotoSansUgaritic.zip":"Noto Sans Ugaritic","NotoSansVai.zip":"Noto Sans Vai","NotoSansWancho.zip":"Noto Sans Wancho","NotoSansWarangCiti.zip":"Noto Sans Warang Citi","NotoSansYi.zip":"Noto Sans Yi","NotoSansZanabazarSquare.zip":"Noto Sans Zanabazar Square","NotoSerifAhom.zip":"Noto Serif Ahom","NotoSerifArmenian.zip":"Noto Serif Armenian","NotoSerifBalinese.zip":"Noto Serif Balinese","NotoSerifBengali.zip":"Noto Serif Bengali","NotoSerifDevanagari.zip":"Noto Serif Devanagari","NotoSerifDisplay.zip":"Noto Serif Display","NotoSerifDogra.zip":"Noto Serif Dogra","NotoSerifEthiopic.zip":"Noto Serif Ethiopic","NotoSerifGeorgian.zip":"Noto Serif Georgian","NotoSerifGrantha.zip":"Noto Serif Grantha","NotoSerifGujarati.zip":"Noto Serif Gujarati","NotoSerifGurmukhi.zip":"Noto Serif Gurmukhi","NotoSerifHebrew.zip":"Noto Serif Hebrew","NotoSerifKannada.zip":"Noto Serif Kannada","NotoSerifKhmer.zip":"Noto Serif Khmer","NotoSerifKhojki.zip":"Noto Serif Khojki","NotoSerifLao.zip":"Noto Serif Lao","NotoSerifMalayalam.zip":"Noto Serif Malayalam","NotoSerifMyanmar.zip":"Noto Serif Myanmar","NotoSerifNyiakengPuachueHmong.zip":"Noto Serif Nyiakeng Puachue Hmong","NotoSerifOriya.zip":"Noto Serif Oriya","NotoSerifSinhala.zip":"Noto Serif Sinhala","NotoSerifTamil.zip":"Noto Serif Tamil","NotoSerifTamilSlanted.zip":"Noto Serif Tamil Slanted","NotoSerifTangut.zip":"Noto Serif Tangut","NotoSerifTelugu.zip":"Noto Serif Telugu","NotoSerifThai.zip":"Noto Serif Thai","NotoSerifTibetan.zip":"Noto Serif Tibetan","NotoSerifVithkuqi.zip":"Noto Serif Vithkuqi","NotoSerifYezidi.zip":"Noto Serif Yezidi","NotoTraditionalNushu.zip":"Noto Traditional Nushu"}';
32
33        $return_array = json_decode( $font_json, true );
34
35        if ( $cleaned_alphabetical ) {
36
37            $cleaned_array = array();
38            foreach ( $return_array as $zip_name => $font_name ) {
39
40                $cleaned_name = $font_name;
41                if ( str_starts_with( $font_name, 'Noto Sans' ) ) {
42                    $cleaned_name = substr( $cleaned_name, 10 ) . ' (Noto Sans)';
43                } elseif ( str_starts_with( $font_name, 'Noto Serif' ) ) {
44                    $cleaned_name = substr( $cleaned_name, 11 ) . ' (Noto Serif)';
45                }
46
47                // special cases here (lets us keep our font array clean but show more info in UI)
48                switch ( $cleaned_name ) {
49
50                    case 'Boku2':
51                        $cleaned_name = 'Boku2 (JP)';
52                        break;
53                    case 'Hong Kong (Noto Sans)':
54                        $cleaned_name = 'Chinese (Traditional, Hong Kong)';
55                        break;
56                    case 'Taiwan (Noto Sans)':
57                        $cleaned_name = 'Chinese (Traditional, Taiwan)';
58                        break;
59                    case 'Simplified Chinese (Noto Sans)':
60                        $cleaned_name = 'Chinese (Simplified)';
61                        break;
62
63                }
64
65                $cleaned_array[ $font_name ] = $cleaned_name;
66
67            }
68
69            // sort alphabetically
70            asort( $cleaned_array );
71
72            return $cleaned_array;
73
74        }
75
76        return $return_array;
77    }
78
79    /*
80    * Converts a font-name to its zip filename
81    */
82    public function zip_to_font_name( $zip_file_name = '' ) {
83
84        return str_replace( '.zip', '', jpcrm_string_split_at_caps( $zip_file_name ) );
85    }
86
87    /*
88    * Converts a font-name to its zip filename
89    */
90    public function font_name_to_zip( $font_name = '' ) {
91
92        return str_replace( ' ', '', $font_name ) . '.zip';
93    }
94
95    /*
96    * Converts a font-name to its *-Regular.ttf filename
97    */
98    public function font_name_to_regular_ttf_name( $font_name = '' ) {
99
100        return str_replace( ' ', '', $font_name ) . '-Regular.ttf';
101    }
102
103    /*
104    * Converts a font-name to its ultimate directory
105    */
106    public function font_name_to_dir( $font_name = '' ) {
107
108        return str_replace( '.zip', '', $this->font_name_to_zip( $font_name ) );
109    }
110
111    /*
112    * Converts a slug to a font name
113    */
114    public function font_slug_to_name( $font_slug = '' ) {
115
116        return ucwords( str_replace( '-', ' ', $font_slug ) );
117    }
118
119    /*
120    * Converts a font-name to a slug equivalent
121    */
122    public function font_name_to_slug( $font_name = '' ) {
123
124        global $zbs;
125        return $zbs->DAL->makeSlug( $font_name );
126    }
127
128    /*
129    * Checks a font is on our available list
130    */
131    public function font_is_available( $font_name = '' ) {
132
133        $fonts = $this->list_all_available();
134
135        if ( isset( $fonts[ $this->font_name_to_zip( $font_name ) ] ) ) {
136
137            return true;
138
139        }
140
141        return false;
142    }
143
144    /*
145    * Checks a font is on our available list
146    */
147    public function font_is_installed( $font_name = '' ) {
148
149        if ( $this->font_is_available( $font_name ) ) {
150
151            // Available?
152            if ( file_exists( ZEROBSCRM_INCLUDE_PATH . 'lib/dompdf-fonts/' . $this->font_name_to_dir( $font_name ) ) ) {
153
154                // Installed? (check setting)
155                $font_install_setting = zeroBSCRM_getSetting( 'pdf_extra_fonts_installed' );
156                if ( ! is_array( $font_install_setting ) ) {
157                    $font_install_setting = array();
158                }
159
160                if ( array_key_exists( $this->font_name_to_slug( $font_name ), $font_install_setting ) ) {
161                    return true;
162                }
163            }
164        }
165
166        return false; // font doesn't exist or isn't installed
167    }
168
169    /*
170    * Installs fonts (which have already been downloaded, but are not marked installed)
171    */
172    public function install_font( $font_name = '', $force_reinstall = false ) {
173
174        // is available, and not installed (or $force_reinstall)
175        if ( $this->font_is_available( $font_name ) &&
176            ( ! $this->font_is_installed( $font_name ) || $force_reinstall )
177        ) {
178
179            // get fonts dir
180            $fonts_dir   = jpcrm_storage_fonts_dir_path();
181            $working_dir = zeroBSCRM_privatisedDirCheckWorks();
182
183            // Check if temp dir is valid
184            if ( empty( $working_dir['path'] ) || ! $fonts_dir ) {
185                return false;
186            }
187
188            $working_dir = $working_dir['path'] . '/';
189
190            $font_directory_name = $this->font_name_to_dir( $font_name );
191
192            // Discern available variations
193            $font_regular_path    = $working_dir . $this->font_name_to_regular_ttf_name( $font_name ); // 'NotoSans-Regular.ttf' - ALL variations have a `*-Regular.ttf` as at 01/12/21
194            $font_bold_path       = null;
195            $font_italic_path     = null;
196            $font_bolditalic_path = null;
197
198            if ( file_exists( $working_dir . $font_directory_name . '-Bold.ttf' ) ) {
199                $font_bold_path = $working_dir . $font_directory_name . '-Bold.ttf';
200            }
201            if ( file_exists( $working_dir . $font_directory_name . '-Italic.ttf' ) ) {
202                $font_italic_path = $working_dir . $font_directory_name . '-Italic.ttf';
203            }
204            if ( file_exists( $working_dir . $font_directory_name . '-BoldItalic.ttf' ) ) {
205                $font_bolditalic_path = $working_dir . $font_directory_name . '-BoldItalic.ttf';
206            }
207
208            // Attempt to install
209            if ( $this->load_font(
210                str_replace( ' ', '', $font_name ), // e.g. NotoSansJP
211                $font_regular_path,
212                $font_bold_path,
213                $font_italic_path,
214                $font_bolditalic_path
215            ) ) {
216
217                global $zbs;
218
219                // Update setting
220                $font_install_setting = $zbs->settings->get( 'pdf_extra_fonts_installed' );
221                if ( ! is_array( $font_install_setting ) ) {
222                    $font_install_setting = array();
223                }
224                $font_install_setting[ $this->font_name_to_slug( $font_name ) ] = time();
225                $zbs->settings->update( 'pdf_extra_fonts_installed', $font_install_setting );
226
227                return true;
228
229            }
230        }
231
232        return false;
233    }
234
235    /*
236    * Installs default fonts (which are extracted, but are not marked installed)
237    * can use $this->extract_and_install_default_fonts() if from scratch (extracts + installs)
238    */
239    public function install_default_fonts( $force_reinstall = false ) {
240
241        global $zbsExtensionInstallError;
242
243        // get fonts dir
244        $fonts_dir   = jpcrm_storage_fonts_dir_path();
245        $working_dir = zeroBSCRM_privatisedDirCheckWorks();
246
247        // Check if temp dir is valid
248        if ( empty( $working_dir['path'] ) || ! $fonts_dir ) {
249            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to create the directories it needs in order to install fonts for the PDF Engine.', 'zero-bs-crm' );
250            return false;
251        }
252
253        $working_dir = $working_dir['path'] . '/';
254
255        // also install the font(s) if not already installed (if present)
256        $fontsInstalled = zeroBSCRM_getSetting( 'pdf_fonts_installed' );
257        if (
258            ( $fontsInstalled !== 1 && file_exists( $fonts_dir . 'fonts-info.txt' ) )
259            ||
260            ( ! $this->default_fonts_installed() )
261            ||
262            $force_reinstall
263        ) {
264
265            // attempt to install
266            if (
267                $this->load_font(
268                    'NotoSansGlobal',
269                    $working_dir . 'NotoSans-Regular.ttf',
270                    $working_dir . 'NotoSans-Bold.ttf',
271                    $working_dir . 'NotoSans-Italic.ttf',
272                    $working_dir . 'NotoSans-BoldItalic.ttf'
273                )
274            ) {
275                // update setting
276                global $zbs;
277                $zbs->settings->update( 'pdf_fonts_installed', 1 );
278
279            } else {
280                $zbsExtensionInstallError = __( 'Jetpack CRM was not able to install fonts for the PDF engine.', 'zero-bs-crm' );
281                return false;
282            }
283        }
284
285        return true;
286    }
287
288    /**
289     * Installs a new font family
290     * This function maps a font-family name to a font.  It tries to locate the
291     * bold, italic, and bold italic versions of the font as well.  Once the
292     * files are located, ttf versions of the font are copied to the fonts
293     * directory.  Changes to the font lookup table are saved to the cache.
294     *
295     * This is an an adapted version of install_font_family() from https://github.com/dompdf/utils
296     *
297     * @param Dompdf      $dompdf dompdf main object.
298     * @param string      $fontname the font-family name.
299     * @param string      $normal the filename of the normal face font subtype.
300     * @param string|null $bold the filename of the bold face font subtype.
301     * @param string|null $italic the filename of the italic face font subtype.
302     * @param string|null $bold_italic the filename of the bold italic face font subtype.
303     *
304     * @throws Exception
305     */
306    public function install_font_family( $dompdf, $fontname, $normal, $bold = null, $italic = null, $bold_italic = null, $debug = false ) {
307
308        try {
309
310            $fontMetrics = $dompdf->getFontMetrics();
311
312            // Check if the base filename is readable
313            if ( ! is_readable( $normal ) ) {
314                throw new Exception( "Unable to read '$normal'." );
315            }
316
317            $dir      = dirname( $normal );
318            $basename = basename( $normal );
319            $last_dot = strrpos( $basename, '.' );
320            if ( $last_dot !== false ) {
321                $file = substr( $basename, 0, $last_dot );
322                $ext  = strtolower( substr( $basename, $last_dot ) );
323            } else {
324                $file = $basename;
325                $ext  = '';
326            }
327
328            // dompdf will eventually support .otf, but for now limit to .ttf
329            if ( ! in_array( $ext, array( '.ttf' ) ) ) {
330                throw new Exception( "Unable to process fonts of type '$ext'." );
331            }
332
333            // Try $file_Bold.$ext etc.
334            $path = "$dir/$file";
335
336            $patterns = array(
337                'bold'        => array( '_Bold', 'b', 'B', 'bd', 'BD' ),
338                'italic'      => array( '_Italic', 'i', 'I' ),
339                'bold_italic' => array( '_Bold_Italic', 'bi', 'BI', 'ib', 'IB' ),
340            );
341
342            foreach ( $patterns as $type => $_patterns ) {
343                if ( ! isset( $$type ) || ! is_readable( $$type ) ) {
344                    foreach ( $_patterns as $_pattern ) {
345                        if ( is_readable( "$path$_pattern$ext" ) ) {
346                            $$type = "$path$_pattern$ext";
347                            break;
348                        }
349                    }
350
351                    if ( $$type === null ) {
352                        if ( $debug ) {
353                            echo ( "Unable to find $type face file.\n" );
354                        }
355                    }
356                }
357            }
358
359            $fonts = compact( 'normal', 'bold', 'italic', 'bold_italic' );
360            $entry = array();
361
362            // Copy the files to the font directory.
363            foreach ( $fonts as $var => $src ) {
364                if ( $src === null ) {
365                    $entry[ $var ] = $dompdf->getOptions()->get( 'fontDir' ) . '/' . mb_substr( basename( $normal ), 0, -4 );
366                    continue;
367                }
368
369                // Verify that the fonts exist and are readable
370                if ( ! is_readable( $src ) ) {
371                    throw new Exception( "Requested font '$src' is not readable" );
372                }
373
374                $dest = $dompdf->getOptions()->get( 'fontDir' ) . '/' . basename( $src );
375
376                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- TODO: Fix these.
377                if ( ! is_writable( dirname( $dest ) ) ) {
378                    throw new Exception( "Unable to write to destination '$dest'." );
379                }
380
381                if ( $debug ) {
382                    echo "Copying $src to $dest...\n";
383                }
384
385                if ( ! copy( $src, $dest ) ) {
386                    throw new Exception( "Unable to copy '$src' to '$dest'" );
387                }
388
389                $entry_name = mb_substr( $dest, 0, -4 );
390
391                if ( $debug ) {
392                    echo "Generating Adobe Font Metrics for $entry_name...\n";
393                }
394
395                $font_obj = Font::load( $dest );
396                $font_obj->saveAdobeFontMetrics( "$entry_name.ufm" );
397                $font_obj->close();
398
399                $entry[ $var ] = $entry_name;
400
401                unlink( $src );
402
403            }
404
405            // Store the fonts in the lookup table
406            $fontMetrics->setFontFamily( $fontname, $entry );
407
408            // Save the changes
409            $fontMetrics->saveFontFamilies();
410
411            // Fini
412            return true;
413
414        } catch ( Exception $e ) {
415
416            // nada
417
418        }
419
420        return false;
421    }
422
423    /*
424    * Retrieves a font zip from our CDN and installs it locally
425    */
426    public function retrieve_and_install_specific_font( $font_name = '', $force_reinstall = false ) {
427
428        global $zbsExtensionInstallError;
429
430        // font exists?
431        if ( ! $this->font_is_available( $font_name ) ) {
432
433            return false;
434
435        }
436
437        // font already installed?
438        if ( $this->font_is_installed( $font_name ) && ! $force_reinstall ) {
439
440            return true;
441
442        }
443
444        // get fonts dir
445        $fonts_dir   = jpcrm_storage_fonts_dir_path();
446        $working_dir = zeroBSCRM_privatisedDirCheckWorks();
447
448        // Check if temp dir is valid
449        if ( empty( $working_dir['path'] ) || ! $fonts_dir ) {
450            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to create the directories it needs in order to install fonts for the PDF Engine.', 'zero-bs-crm' );
451            return false;
452        }
453
454        $working_dir    = $working_dir['path'] . '/';
455        $font_file_name = $this->font_name_to_regular_ttf_name( $font_name );
456
457        // Already downloaded, so proceed to install
458        if ( file_exists( $working_dir . $font_file_name ) ) {
459            return $this->install_font( $font_name );
460        }
461
462        // Retrieve & install the font
463        $remote_zip_name = $this->font_name_to_zip( $font_name );
464        $temp_path       = tempnam( sys_get_temp_dir(), 'crmfont' );
465
466        // Several large font files may timeout when downloading. Increase the timeout for these files.
467        $large_font_files = array( ' Noto  Sans  Simplified  Chinese', ' Noto  Serif  Display', ' Noto  Sans', ' Noto  Sans  Display', ' Noto  Sans  Taiwan', ' Noto  Sans  Hong  Kong', ' Noto  Sans  Japanese', ' Noto  Sans  Korean', ' Noto  Sans  Mono' );
468        if ( in_array( $font_name, $large_font_files, true ) ) {
469            add_filter(
470                'http_request_timeout',
471                function () {
472                    return 60;
473                }
474            );
475        }
476
477        // Retrieve zip
478        global $zbs;
479        if ( ! zeroBSCRM_retrieveFile( $zbs->urls['extdlfonts'] . $remote_zip_name, $temp_path ) ) {
480            // Something failed
481            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to download the fonts it needs for the PDF Engine.', 'zero-bs-crm' ) . ' ' . __( '(fonts)', 'zero-bs-crm' );
482            unlink( $temp_path );
483            return false;
484        }
485
486        // Extract zip to fonts dir
487        $expanded = zeroBSCRM_expandArchive( $temp_path, $working_dir );
488        unlink( $temp_path );
489
490        // appears to have worked
491        if ( ! $expanded || ! file_exists( $working_dir . $font_file_name ) ) {
492
493            // Add error msg
494            $zbsExtensionInstallError = __( 'CRM was not able to retrieve the requested font.', 'zero-bs-crm' ) . ' ' . __( '(Failed to install font.)', 'zero-bs-crm' );
495            ##WLREMOVE
496            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to retrieve the requested font.', 'zero-bs-crm' ) . ' ' . __( '(Failed to install font.)', 'zero-bs-crm' );
497            ##/WLREMOVE
498
499            return false;
500
501        }
502
503        // install the font
504        return $this->install_font( $font_name, true );
505    }
506
507    /*
508    * Extract (and install) default fonts which dompdf uses to provide global lang supp
509    * This function is used if somebody were to delete these default fonts from jpcrm-storage.
510    * Instead, use retrieve_and_install() to retrieve locale specific fonts (from v4.7.0)
511    */
512    public function extract_and_install_default_fonts() {
513
514        global $zbsExtensionInstallError;
515
516        // get fonts dir
517        $fonts_dir   = jpcrm_storage_fonts_dir_path();
518        $working_dir = zeroBSCRM_privatisedDirCheckWorks();
519
520        // Check if temp dir is valid
521        if ( empty( $working_dir['path'] ) || ! $fonts_dir ) {
522            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to create the directories it needs in order to install fonts for the PDF Engine.', 'zero-bs-crm' );
523            return false;
524        }
525
526        $working_dir = $working_dir['path'] . '/';
527
528        // Check if fonts are already downloaded
529        if ( file_exists( $fonts_dir . 'fonts-info.txt' ) ) {
530            // Proceed to installation
531            return $this->install_default_fonts();
532        }
533
534        // Extract zip to fonts dir
535        $expanded = zeroBSCRM_expandArchive( ZEROBSCRM_PATH . 'data/pdffonts.zip', $working_dir );
536
537        // Check success?
538        if ( ! $expanded || ! file_exists( $working_dir . 'fonts-info.txt' ) ) {
539
540            // Add error msg
541            $zbsExtensionInstallError = __( 'Jetpack CRM was not able to extract the fonts it needs for the PDF Engine.', 'zero-bs-crm' ) . ' ' . __( '(fonts)', 'zero-bs-crm' );
542            // Return fail
543            return false;
544
545        }
546
547        rename( $working_dir . 'fonts-info.txt', $fonts_dir . 'fonts-info.txt' );
548
549        // install 'em
550        return $this->install_default_fonts( true );
551    }
552
553    /**
554     * Loads a font file collection (.ttf's) onto the server for dompdf
555     * only needs to fire once
556     *
557     * @param string      $font_name Font name.
558     * @param string      $regular_file Regular font file.
559     * @param string|null $bold_file Bold font file.
560     * @param string|null $italic_file Italic font file.
561     * @param string|null $bold_italic_file Bold italic font file.
562     */
563    public function load_font( $font_name = '', $regular_file = '', $bold_file = null, $italic_file = null, $bold_italic_file = null ) {
564
565        if (
566            zeroBSCRM_isZBSAdminOrAdmin()
567            && ! empty( $font_name )
568            && file_exists( $regular_file )
569            && ( $bold_file === null || file_exists( $bold_file ) )
570            && ( $italic_file === null || file_exists( $italic_file ) )
571            && ( $bold_italic_file === null || file_exists( $bold_italic_file ) )
572        ) {
573
574            // PDF Install check (importantly skip the font check with false first param)
575            zeroBSCRM_extension_checkinstall_pdfinv( false );
576
577            global $zbs;
578            $dompdf = $zbs->pdf_engine();
579
580            // Install the font(s)
581            return $this->install_font_family( $dompdf, $font_name, $regular_file, $bold_file, $italic_file, $bold_italic_file );
582
583        }
584
585        return false;
586    }
587
588    /*
589    * Retrieves the font cache from dompdf and returns all loaded fonts
590    */
591    public function loaded_fonts() {
592
593        $dompdf_font_cache_file = jpcrm_storage_fonts_dir_path() . 'installed-fonts.json';
594
595        if ( file_exists( $dompdf_font_cache_file ) ) {
596
597            $cacheData = json_decode( file_get_contents( $dompdf_font_cache_file ) );
598
599            return $cacheData;
600
601        }
602
603        return array();
604    }
605
606    /*
607    * Returns bool whether or not our key font (Noto Sans global) is installed according to dompdf
608    */
609    public function default_fonts_installed() {
610
611        $existing_fonts = $this->loaded_fonts();
612
613        if ( isset( $existing_fonts->notosansglobal ) ) {
614
615            return true;
616        }
617
618        return false;
619    }
620}