@kirigami/php-prepros 1.1.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1593 -869
- package/index.d.ts +145 -2
- package/index.js +1 -1
- package/package.json +4 -4
- package/src/imagebatch.php +67 -0
- package/src/libraries/aliases.inc.php +836 -0
- package/src/libraries/curl.class.php +20 -3
- package/src/libraries/fs.class.php +161 -6
- package/src/libraries/html.class.php +92 -22
- package/src/libraries/img.class.php +372 -17
- package/src/libraries/ld.class.php +804 -0
- package/src/libraries/md.class.php +502 -173
- package/src/libraries/md.plugins.php +25 -19
- package/src/libraries/normalizer.class.php +310 -0
- package/src/libraries/prepros.class.php +149 -10
- package/src/libraries/prepros.plugins.php +56 -6
- package/src/libraries/schema.class.php +550 -0
- package/src/libraries/scraper.class.php +64 -59
- package/src/libraries/std.class.php +3 -0
- package/src/libraries/str.class.php +50 -16
- package/src/libraries/yaml.class.php +66 -66
- package/src/phpjs/fstat.js +9 -0
- package/src/prepros.js +126 -47
- package/src/prepros.php +9 -2
- package/src/runenv.php +5 -2
- package/src/utils/getfilestats.js +30 -0
- package/src/utils/isbinary.js +14 -14
- package/src/utils.inc.php +20 -9
|
@@ -98,45 +98,45 @@ class SCRAPER
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
if(($results = $xpath->query('//meta[@name="dc.title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
|
|
102
|
-
if(($results = $xpath->query('//meta[@name="dcterms.title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
|
|
103
|
-
if(($results = $xpath->query('//meta[@name="twitter:title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
|
|
104
|
-
if(($results = $xpath->query('//meta[@property="og:title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
|
|
105
|
-
if(($results = $xpath->query('//shreddit-title')) && $results->length) $metas->title = getFirstItemAttr($results, 'title');
|
|
106
|
-
if(($results = $xpath->query('//meta[@name="dc.description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
|
|
107
|
-
if(($results = $xpath->query('//meta[@name="dcterms.description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
|
|
108
|
-
if(($results = $xpath->query('//meta[@name="twitter:description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
|
|
109
|
-
if(($results = $xpath->query('//meta[@property="og:description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
|
|
110
|
-
if(($results = $xpath->query('//meta[@name="description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
|
|
111
|
-
if(($results = $xpath->query('//meta[@name="twitter:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
|
|
112
|
-
if(($results = $xpath->query('//meta[@property="og:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
|
|
113
|
-
if(($results = $xpath->query('//meta[@name="og:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
|
|
114
|
-
if(($results = $xpath->query('//img[@rel="og:image rdfs:seeAlso"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
|
|
115
|
-
if(($results = $xpath->query('//link[@rel="canonical"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'href');
|
|
116
|
-
if(($results = $xpath->query('//meta[@name="twitter:url"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'content');
|
|
117
|
-
if(($results = $xpath->query('//meta[@property="og:url"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'content');
|
|
118
|
-
|
|
119
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:iphone"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
120
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:googleplay"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
121
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="og:site_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
122
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="og:site_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
123
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dc.publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
124
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.Publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
125
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
126
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="article:author"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
127
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="author"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
128
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:creator"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
129
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dcterms.creator"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
130
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:ios:app_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
131
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:android:app_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
132
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
|
|
101
|
+
if(($results = $xpath->query('//meta[@name="dc.title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
|
|
102
|
+
if(($results = $xpath->query('//meta[@name="dcterms.title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
|
|
103
|
+
if(($results = $xpath->query('//meta[@name="twitter:title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
|
|
104
|
+
if(($results = $xpath->query('//meta[@property="og:title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
|
|
105
|
+
if(($results = $xpath->query('//shreddit-title')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'title');
|
|
106
|
+
if(($results = $xpath->query('//meta[@name="dc.description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
|
|
107
|
+
if(($results = $xpath->query('//meta[@name="dcterms.description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
|
|
108
|
+
if(($results = $xpath->query('//meta[@name="twitter:description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
|
|
109
|
+
if(($results = $xpath->query('//meta[@property="og:description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
|
|
110
|
+
if(($results = $xpath->query('//meta[@name="description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
|
|
111
|
+
if(($results = $xpath->query('//meta[@name="twitter:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
|
|
112
|
+
if(($results = $xpath->query('//meta[@property="og:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
|
|
113
|
+
if(($results = $xpath->query('//meta[@name="og:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
|
|
114
|
+
if(($results = $xpath->query('//img[@rel="og:image rdfs:seeAlso"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
|
|
115
|
+
if(($results = $xpath->query('//link[@rel="canonical"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'href');
|
|
116
|
+
if(($results = $xpath->query('//meta[@name="twitter:url"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'content');
|
|
117
|
+
if(($results = $xpath->query('//meta[@property="og:url"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'content');
|
|
118
|
+
|
|
119
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:iphone"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
120
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:googleplay"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
121
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="og:site_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
122
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="og:site_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
123
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dc.publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
124
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.Publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
125
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
126
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="article:author"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
127
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="author"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
128
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:creator"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
129
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dcterms.creator"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
130
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:ios:app_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
131
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:android:app_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
132
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
|
|
133
133
|
if(empty($metas->label) && ($results = $xpath->query('//span[@class="footer__signature"]')) && $results->length) $metas->label = $results->item(0)->textContent;
|
|
134
134
|
if(empty($metas->label) && ($results = $xpath->query('//p[@class="site-title"]')) && $results->length) $metas->label = trim($results->item(0)->textContent);
|
|
135
|
-
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="copyright"]')) && $results->length) $metas->label = trim(getFirstItemAttr($results, 'content'), '© ');
|
|
135
|
+
if(empty($metas->label) && ($results = $xpath->query('//meta[@name="copyright"]')) && $results->length) $metas->label = trim(self::getFirstItemAttr($results, 'content'), '© ');
|
|
136
136
|
if(empty($metas->label) && preg_match("#\{'blogId': '[0-9]+', 'title': '([^']+)'#i", $contents, $m)) $metas->label = $m[1];
|
|
137
137
|
|
|
138
138
|
if(($results = $xpath->query('//link[@rel="alternate" and @type="application/json+oembed" and @href]')) && $results->length) {
|
|
139
|
-
if(STR::is_url(($oembedurl = getFirstItemAttr($results, 'href')))) {
|
|
139
|
+
if(STR::is_url(($oembedurl = self::getFirstItemAttr($results, 'href')))) {
|
|
140
140
|
if($json = self::getContents($oembedurl)) {
|
|
141
141
|
if($oem = json_decode($json)) {
|
|
142
142
|
if(empty($metas->title) && !empty($oem->title)) $metas->title = $oem->title;
|
|
@@ -171,7 +171,7 @@ class SCRAPER
|
|
|
171
171
|
}
|
|
172
172
|
if(empty($metas->label)) {
|
|
173
173
|
if(($results = $xpath->query('//link[@rel="manifest" and @href]')) && $results->length) {
|
|
174
|
-
if($manifesturl = get_absolute_url($url, getFirstItemAttr($results, 'href'))) {
|
|
174
|
+
if($manifesturl = get_absolute_url($url, self::getFirstItemAttr($results, 'href'))) {
|
|
175
175
|
if($json = self::getContents($manifesturl)) {
|
|
176
176
|
if($manifest = json_decode($json)) {
|
|
177
177
|
if(!empty($manifest->name)) $metas->label = $manifest->name;
|
|
@@ -181,10 +181,10 @@ class SCRAPER
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
if(empty($metas->label) && (($results = $xpath->query('//meta[@property="fb:page_id"]')) && $results->length)) {
|
|
184
|
-
if($_contents = self::getContents('https://facebook.com/' . getFirstItemAttr($results, 'content'))) {
|
|
184
|
+
if($_contents = self::getContents('https://facebook.com/' . self::getFirstItemAttr($results, 'content'))) {
|
|
185
185
|
if($xp = self::loadHTML($_contents)) {
|
|
186
186
|
if(($results = $xp->query('//meta[@property="og:title"]')) && $results->length) {
|
|
187
|
-
$metas->label = getFirstItemAttr($results, 'content');
|
|
187
|
+
$metas->label = self::getFirstItemAttr($results, 'content');
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -196,14 +196,14 @@ class SCRAPER
|
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
if(empty($metas->image) && ($results = $xpath->query('//span[@class="background-image not-mobile"]')) && $results->length)
|
|
199
|
-
if(preg_match('#url\(\'(.*?)\'\)#i', getFirstItemAttr($results, 'style'), $m))
|
|
199
|
+
if(preg_match('#url\(\'(.*?)\'\)#i', self::getFirstItemAttr($results, 'style'), $m))
|
|
200
200
|
$metas->image = $m[1];
|
|
201
201
|
|
|
202
|
-
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/jpeg"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
|
|
203
|
-
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/png"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
|
|
204
|
-
if(empty($metas->image) && ($results = $xpath->query('//article//img')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
|
|
205
|
-
if(empty($metas->image) && ($results = $xpath->query('//div[@id="image_box"]//img')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
|
|
206
|
-
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="shortcut icon" and @type="image/jpeg" and @href]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
|
|
202
|
+
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/jpeg"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
|
|
203
|
+
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/png"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
|
|
204
|
+
if(empty($metas->image) && ($results = $xpath->query('//article//img')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
|
|
205
|
+
if(empty($metas->image) && ($results = $xpath->query('//div[@id="image_box"]//img')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
|
|
206
|
+
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="shortcut icon" and @type="image/jpeg" and @href]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
|
|
207
207
|
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon" and @sizes]')) && $results->length) {
|
|
208
208
|
$size = 0;
|
|
209
209
|
foreach($results as $item) {
|
|
@@ -215,14 +215,14 @@ class SCRAPER
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
-
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
|
|
218
|
+
if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
|
|
219
219
|
if(empty($metas->title) && ($results = $xpath->query('//title')) && $results->length) $metas->title = $results->item(0)->textContent;
|
|
220
220
|
if(empty($metas->label)) { $parts = explode('|', $metas->title); if(count($parts) > 1) $metas->label = trim(array_pop($parts)); }
|
|
221
221
|
if(empty($metas->label)) { $parts = explode(' - ', $metas->title); if(count($parts) > 1) $metas->label = trim(array_pop($parts)); }
|
|
222
222
|
if(strpos($metas->label, '@') === 0) $metas->label = ucwords(preg_replace('#^@#i', '', $metas->label));
|
|
223
223
|
|
|
224
224
|
if(empty($metas->label) && ($results = $xpath->query('//div[@id="elevate-global" and @data-elevate-global]')) && $results->length)
|
|
225
|
-
if($data = json_decode(getFirstItemAttr($results, 'data-elevate-global')))
|
|
225
|
+
if($data = json_decode(self::getFirstItemAttr($results, 'data-elevate-global')))
|
|
226
226
|
if(!empty($data->content_organization))
|
|
227
227
|
$metas->label = $data->content_organization;
|
|
228
228
|
|
|
@@ -248,9 +248,9 @@ class SCRAPER
|
|
|
248
248
|
if($_contents = self::getContents(str_replace('\\/', '/', $m[1])))
|
|
249
249
|
if($xp = self::loadHTML($_contents))
|
|
250
250
|
if(($results = $xp->query('//link[@rel="preload" and @as="image" and contains(@href, ".jpg")]')) && $results->length)
|
|
251
|
-
$metas->image = getFirstItemAttr($results, 'href');
|
|
251
|
+
$metas->image = self::getFirstItemAttr($results, 'href');
|
|
252
252
|
|
|
253
|
-
if(!url_exists($metas->image, '^image/') && ($results = $xpath->query('//img[@src]')) && $results->length) {
|
|
253
|
+
if(!self::url_exists($metas->image, '^image/') && ($results = $xpath->query('//img[@src]')) && $results->length) {
|
|
254
254
|
foreach($results as $item) {
|
|
255
255
|
/** @var DOMElement $item */
|
|
256
256
|
if(in_array(pathinfo(parse_url($item->getAttribute('src'), PHP_URL_PATH), PATHINFO_EXTENSION), ['jpg', 'jpeg', 'png', 'webp'])) {
|
|
@@ -301,20 +301,25 @@ class SCRAPER
|
|
|
301
301
|
return $metas;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
}
|
|
305
304
|
|
|
305
|
+
private static function getFirstItemAttr(\Dom\Node|\Dom\NodeList $results, string $attr) {
|
|
306
|
+
/** @var DOM\Element $item */
|
|
307
|
+
$item = $results->item(0);
|
|
308
|
+
return $item->getAttribute($attr);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
private static function url_exists(string $url, string|null $mimereg = null) {
|
|
313
|
+
if(($exists = CACHE::get(($key = 'urlexists_' . STR::shorthash($url)))) !== null) return $exists->response;
|
|
314
|
+
$exists = new stdClass;
|
|
315
|
+
$exists->response = CURL::urlExists($url, $mimereg);
|
|
316
|
+
CACHE::set($key, $exists);
|
|
317
|
+
return $exists->response;
|
|
318
|
+
}
|
|
306
319
|
|
|
307
|
-
function getFirstItemAttr(\Dom\Node|\Dom\NodeList $results, string $attr) {
|
|
308
|
-
/** @var DOM\Element $item */
|
|
309
|
-
$item = $results->item(0);
|
|
310
|
-
return $item->getAttribute($attr);
|
|
311
320
|
}
|
|
312
321
|
|
|
313
322
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
$exists->response = CURL::urlExists($url, $mimereg);
|
|
318
|
-
CACHE::set($key, $exists);
|
|
319
|
-
return $exists->response;
|
|
320
|
-
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
@@ -24,6 +24,9 @@ class STD {
|
|
|
24
24
|
$return->files = PREPROS::getExportedFiles();
|
|
25
25
|
if(is_string($props)) $return->error = $props;
|
|
26
26
|
else foreach($props as $k => $v) $return->{$k} = $v;
|
|
27
|
+
// Consumers read `->error`; keep it populated whatever key the caller
|
|
28
|
+
// used (some pass `message`) so nothing ever surfaces as "undefined".
|
|
29
|
+
if(empty($return->error)) $return->error = $return->message ?? 'Unknown error.';
|
|
27
30
|
file_put_contents(self::RESULT_PATH, json_encode($return, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), FILE_APPEND);
|
|
28
31
|
exit(1);
|
|
29
32
|
}
|
|
@@ -12,21 +12,51 @@ class STR
|
|
|
12
12
|
|
|
13
13
|
public static function replaceTags(string $tag, string $contents, callable $clb): string
|
|
14
14
|
{
|
|
15
|
-
$
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
$t = preg_quote($tag, '#');
|
|
16
|
+
|
|
17
|
+
$pattern = '#<' . $t . '([^>]*)>(.*?)</' . $t . '>'
|
|
18
|
+
. '|<' . $t . '([^>]*)/>'
|
|
19
|
+
. '|<' . $t . '([^>]*)>#msi';
|
|
20
|
+
|
|
21
|
+
return preg_replace_callback($pattern, function ($m) use ($clb) {
|
|
22
|
+
if (isset($m[1]) || isset($m[2])) {
|
|
23
|
+
// paired form: <tag>content</tag>
|
|
24
|
+
$attrs = $m[1] ?? '';
|
|
25
|
+
$inner = $m[2] ?? '';
|
|
26
|
+
} elseif (isset($m[3])) {
|
|
27
|
+
// self-closing: <tag ... />
|
|
28
|
+
$attrs = $m[3];
|
|
29
|
+
$inner = '';
|
|
30
|
+
} else {
|
|
31
|
+
// opening only, no closing (img, meta, br, ...)
|
|
32
|
+
$attrs = $m[4] ?? '';
|
|
33
|
+
$inner = '';
|
|
34
|
+
}
|
|
35
|
+
return call_user_func($clb, $m[0], self::parseHtmlAttributes($attrs), $inner);
|
|
36
|
+
}, $contents, -1, $count, PREG_UNMATCHED_AS_NULL); // <-- the flag that fixes everything
|
|
19
37
|
}
|
|
20
38
|
|
|
21
39
|
|
|
22
40
|
public static function parseHtmlAttributes(string $attributes): array
|
|
23
41
|
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
$attrs = [];
|
|
43
|
+
if (preg_match_all('#(\w+)(?:\s*=\s*("[^"]*"|\'[^\']*\'|[^"\'\s>]*))?#i', $attributes, $m, PREG_SET_ORDER)) {
|
|
44
|
+
foreach ($m as $match) {
|
|
45
|
+
$key = strtolower($match[1]);
|
|
46
|
+
if (isset($match[2]) && $match[2] !== '') {
|
|
47
|
+
$value = $match[2];
|
|
48
|
+
// strip the quotes only if they're present
|
|
49
|
+
if (($value[0] === '"' || $value[0] === "'") && $value[0] === substr($value, -1)) {
|
|
50
|
+
$value = substr($value, 1, -1);
|
|
51
|
+
}
|
|
52
|
+
$attrs[$key] = stripslashes($value);
|
|
53
|
+
} else {
|
|
54
|
+
// boolean attribute: selected, muted, disabled, checked...
|
|
55
|
+
$attrs[$key] = true;
|
|
56
|
+
}
|
|
27
57
|
}
|
|
28
58
|
}
|
|
29
|
-
return
|
|
59
|
+
return $attrs;
|
|
30
60
|
}
|
|
31
61
|
|
|
32
62
|
|
|
@@ -48,33 +78,37 @@ class STR
|
|
|
48
78
|
|
|
49
79
|
public static function is_url(string $str): bool
|
|
50
80
|
{
|
|
51
|
-
if (!$sheme = strtolower(@parse_url($str, PHP_URL_SCHEME))) return false;
|
|
81
|
+
if (!$sheme = strtolower((string) @parse_url($str, PHP_URL_SCHEME))) return false;
|
|
52
82
|
return in_array($sheme, ['http', 'https', 'itunes', 'ftp', 'ftps', 'ssh', 'ssl', 'sftp']);
|
|
53
83
|
}
|
|
54
84
|
|
|
55
85
|
|
|
56
86
|
public static function html_entities_decode(string $str): string
|
|
57
87
|
{
|
|
58
|
-
// $str = html_entity_decode(trim($str), ENT_QUOTES, 'UTF-8');
|
|
59
|
-
// $str = htmlentities($str, ENT_QUOTES, 'UTF-8');
|
|
60
|
-
// $str = mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8');
|
|
61
88
|
$str = html_entity_decode(trim($str), ENT_QUOTES, 'UTF-8');
|
|
62
|
-
// $str = htmlentities($str, ENT_QUOTES, 'UTF-8');
|
|
63
89
|
return $str;
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
|
|
67
|
-
public static function shorthash(string$str): string
|
|
93
|
+
public static function shorthash(string $str): string
|
|
68
94
|
{
|
|
69
95
|
return substr(hash('sha256', $str), 0, 12);
|
|
70
96
|
}
|
|
71
97
|
|
|
72
98
|
|
|
73
|
-
public static function
|
|
99
|
+
public static function normalize(string $str) {
|
|
100
|
+
$str = Normalizer::normalize($str, Normalizer::FORM_D);
|
|
101
|
+
$str = preg_replace('/\p{Mn}/u', '', $str);
|
|
102
|
+
return $str;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
public static function slug(string $str, $sep = ''): string
|
|
74
107
|
{
|
|
108
|
+
$str = self::normalize($str);
|
|
75
109
|
$str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
|
76
110
|
$str = strtolower($str);
|
|
77
|
-
$str = preg_replace('/[^a-z0-9]/',
|
|
111
|
+
$str = preg_replace('/[^a-z0-9]/', $sep, $str);
|
|
78
112
|
return $str;
|
|
79
113
|
}
|
|
80
114
|
|