| 150 | | /** |
| 151 | | * Gets first block of asterisk /* comment or # hash or // slash comment, |
| 152 | | * removes leading whitespace and comment characters |
| 153 | | * |
| 154 | | * @param string $src The source |
| 155 | | */ |
| 156 | | private function extract_first_comment($src) |
| 157 | | { |
| 158 | | |
| 159 | | // clean out first line |
| 160 | | $src = preg_replace("/^<\?(php)?[^\n]*/i", "", $src); |
| 161 | | |
| 162 | | // extract /* ... */ comment block or lines of #... #... and //... //... |
| 163 | | if (preg_match("_^\s*/\*+(.+?)\*+/_s", $src, $uu) || (preg_match("_^\s*((^\s*(#+|//+)\s*.+?$\n)+)_ms", $src, $uu))) { |
| 164 | | $src = $uu[1]; |
| 165 | | } else { |
| 166 | | return; |
| 167 | | } |
| 168 | | |
| 169 | | // cut comment/whitespace prefixes like _*__ or __#_ or _//__ from |
| 170 | | // lines - with same length from everyone! - don't care about actual |
| 171 | | // pattern, but allow shortened lines (missing spaces after # or *) |
| 172 | | preg_match("_^([*#/ ]+)\w+( \w+)?:_m", $src, $uu); |
| 173 | | if ($uu) { |
| 174 | | $n = strlen($uu[1]); |
| 175 | | $src = preg_replace("_^[*#/ ]{0,$n}_m", "", $src); |
| 176 | | return($src); |
| 177 | | } |
| 178 | | |
| 179 | | return false; |
| 180 | | } |
| 181 | | |
| 182 | | /** |
| 183 | | * Reads in <em>.php</em> plugin meta data from given directory and up-to three subdirectories. |
| 184 | | * The data gets stored into <em>$this->$plugin</em> for later use, augmented |
| 185 | | * by every plugins filename relative to the supplied basedir. |
| 186 | | * |
| 187 | | * @param string $basedir the folder in which to look into |
| 188 | | * @return array |
| 189 | | */ |
| 190 | | public function scan($basedir) |
| 191 | | { |
| 192 | | |
| 193 | | // reading in |
| 194 | | $basedir = realpath($basedir); |
| 195 | | |
| 196 | | // each file |
| 197 | | foreach ($this->scan_subdirs($basedir) as $num => $fn) { |
| 198 | | |
| 199 | | // basename == id |
| 200 | | $id = basename($fn, ".php"); |
| 201 | | |
| 202 | | // parse |
| 203 | | if ($e = $this->read($fn)) { |
| 204 | | |
| 205 | | // has plugin custom set id: ? (should not happen, but who knows if this might be useful?) |
| 206 | | if (!empty($e["id"])) { |
| 207 | | $id = $e["id"]; |
| 208 | | } else { |
| 209 | | $e["id"] = $id; |
| 210 | | } |
| 211 | | |
| 212 | | // WordPress plugin scheme compatibility |
| 213 | | if ($e["plugin name"]) { |
| 214 | | // allows to read in WordPress plugin comment too, |
| 215 | | // http://codex.wordpress.org/Writing_a_Plugin |
| 216 | | $wp_compat = array("plugin name" => "title", "description" => "description", "plugin uri" => "url", "author uri" => "author_url"); |
| 217 | | foreach ($this->wp_compat as $from => $to) |
| 218 | | if (!isset($e[$to])) { |
| 219 | | $e[$to] = $e[$from]; |
| 220 | | unset($e[$from]); |
| 221 | | } |
| 222 | | $e["api"] = "wordpress"; |
| 223 | | } |
| 224 | | |
| 225 | | // add localized filename |
| 226 | | $fn = substr($fn, strlen($basedir) + 1); |
| 227 | | $e["fn"] = $fn; |
| 228 | | |
| 229 | | // append to list |
| 230 | | if (isset($this->plugin[$id])) { |
| 231 | | $this->error("a plugin with the name '$id' is already registered (second_fn=$fn, registered={$this->plugin[$id][fn]})\n"); |
| 232 | | } else { |
| 233 | | $this->plugin[$id] = $e; |
| 234 | | } |
| 235 | | } |
| 236 | | } |
| 237 | | |
| 238 | | // plugin dependencies |
| 239 | | $this->extract_lists(); |
| 240 | | |
| 241 | | // send it back even if probably unused |
| 242 | | return $this->plugin; |
| 243 | | } |
| 244 | | |
| 245 | | /** |
| 246 | | * Separates dependency fields and config variables out of all plugin entries |
| 247 | | */ |
| 248 | | private function extract_lists() |
| 249 | | { |
| 250 | | |
| 251 | | // extract list fields |
| 252 | | $fields = array("depends", "suggests", "replaces", "conflicts"); |
| 253 | | foreach ($this->plugin as $id => $e) { |
| 254 | | |
| 255 | | // provides: |
| 256 | | if ($e["provides"]) |
| 257 | | foreach (explode(",", $e["provides"]) as $set) |
| 258 | | if ($set = trim($set)) { |
| 259 | | $this->provides[$set][] = $id; // only the first gets used |
| 260 | | } |
| 261 | | |
| 262 | | // depends: |
| 263 | | foreach ($fields as $field) { |
| 264 | | if (isset($e[$field])) { |
| 265 | | foreach (explode(",", $e["depends"]) as $set) { |
| 266 | | if ($set = trim($set)) { |
| 267 | | $this->{$field}[$id][] = $set; |
| 268 | | } |
| 269 | | } |
| 270 | | } |
| 271 | | } |
| 272 | | |
| 273 | | // config: |
| 274 | | if ($e["config"]) { |
| 275 | | // currently doesn't get unfold into plugin $e entry, |
| 276 | | // but just into $this->config[] list |
| 277 | | $cfg_txt = $e["config"]; |
| 278 | | foreach ($this->parse_options($cfg_txt, $id) as $opt) { |
| 279 | | $this->config[$opt["name"]] = $opt; |
| 280 | | } |
| 281 | | } |
| 282 | | } |
| 283 | | } |
| 284 | | |
| 285 | | /** |
| 286 | | * Look for <em>.php</em> files in subdirectories |
| 287 | | * |
| 288 | | * @param string $basedir the path in which to look |
| 289 | | * @return array An array with the paths. |
| 290 | | */ |
| 291 | | private function scan_subdirs($basedir) |
| 292 | | { |
| 293 | | $r = array(); |
| 294 | | if ($dh = opendir($basedir)) { |
| 295 | | while ($fn = readdir($dh)) { |
| 296 | | if ($fn[0] != ".") { |
| 297 | | if (is_dir("$basedir/$fn")) { |
| 298 | | foreach ($this->scan_subdirs("$basedir/$fn") as $fn) { |
| 299 | | $r[] = $fn; |
| 300 | | } |
| 301 | | } elseif (strpos($fn, ".php")) { |
| 302 | | $r[] = "$basedir/$fn"; |
| 303 | | } |
| 304 | | } |
| 305 | | } |
| 306 | | closedir($dh); |
| 307 | | } |
| 308 | | return $r; |
| 309 | | } |
| 310 | | |
| 311 | | /** |
| 312 | | * Extract <em>config:</em> options pseudo XML into array. |
| 313 | | * |
| 314 | | * <code> |
| 315 | | * <var name="app[setting]" value="default_1" title=".." /> |
| 316 | | * -> |
| 317 | | * array( |
| 318 | | * "is" => "var", |
| 319 | | * "plugin" => "config_option_was_found_in_this_php_plugin_script", |
| 320 | | * "name" => "app[setting]", |
| 321 | | * "value" => "default_1", |
| 322 | | * "title" => "name of first setting", |
| 323 | | * "description" => "should better be present", |
| 324 | | * # "multi" => array("val1" => "title1", "v2" => "t2", ...), |
| 325 | | * # "type" => "text", |
| 326 | | * # "..." => "depending on var type, could have other options", |
| 327 | | * ) |
| 328 | | * </code> |
| 329 | | * |
| 330 | | * @param string $str |
| 331 | | * @param string $plugin |
| 332 | | * @return array An array per config option / varname |
| 333 | | */ |
| 334 | | private function parse_options($str, $plugin) |
| 335 | | { |
| 336 | | $r = array(); |
| 337 | | |
| 338 | | // search for < angle brackets > first |
| 339 | | preg_match_all("_<(\w+)(.+?)/\s*>_ims", $str, $uu); |
| 340 | | foreach ($uu[1] as $i => $optiontype) { |
| 341 | | $inner = $uu[2][$i]; |
| 342 | | |
| 343 | | // prepare new |
| 344 | | $entry = array( |
| 345 | | "is" => $optiontype, |
| 346 | | "plugin" => $plugin, |
| 347 | | ); |
| 348 | | |
| 349 | | // extract individual fields |
| 350 | | preg_match_all("_\s+([-\w:]+)=[\"\']([^\"\']*?)[\"\']_msi", $inner, $vv); |
| 351 | | foreach ($vv[1] as $j => $field) { |
| 352 | | $entry[$field] = $vv[2][$j]; |
| 353 | | } |
| 354 | | |
| 355 | | // clean name= |
| 356 | | $entry["name"] = preg_replace("/[\$\"\'\s]/", "", $entry["name"]); |
| 357 | | |
| 358 | | // split up multi= value (our value= field holds the default entry instead) |
| 359 | | if (strpos($entry["multi"], "|")) { |
| 360 | | $opt = array(); |
| 361 | | foreach (explode("|", $entry["multi"]) as $o) { |
| 362 | | if (strpos($o, "=")) { |
| 363 | | $opt[strtok($o, "=")] = strtok("\n"); |
| 364 | | } else { |
| 365 | | $opt[$o] = $o; |
| 366 | | } |
| 367 | | } |
| 368 | | $entry["multi"] = $opt; |
| 369 | | } |
| 370 | | |
| 371 | | // rename, just in case (actually default= is not recommended, and value= should be used) |
| 372 | | if (isset($row["default"]) && empty($row["value"])) { |
| 373 | | $row["value"] = $row["default"]; |
| 374 | | } |
| 375 | | |
| 376 | | // add to list |
| 377 | | $r[] = $entry; |
| 378 | | } |
| 379 | | return($r); |
| 380 | | } |
| 381 | | |
| 382 | | /** |
| 383 | | * Returns plugins grouped by entries. |
| 384 | | * |
| 385 | | * @param string $field value - or comparison value |
| 386 | | * @param string $cmp |
| 387 | | * @return array |
| 388 | | */ |
| 389 | | public function by($field, $cmp=NULL) |
| 390 | | { |
| 391 | | $r = array(); |
| 392 | | foreach ($this->plugin as $id => $row) { |
| 393 | | if ((empty($cmp) && isset($row[$field])) || ($cmp == strtolower($row[$field]))) { |
| 394 | | $r[$row[$field]][$id] = $row; |
| 395 | | } |
| 396 | | } |
| 397 | | return $r; |
| 398 | | } |
| 399 | | |
| 400 | | /** |
| 401 | | * Complain |
| 402 | | * @param string $s the error message |
| 403 | | */ |
| 404 | | private function error($s) |
| 405 | | { |
| 406 | | trigger_error($s, E_USER_WARNING); |
| 407 | | $this->error = 1; |
| 408 | | } |
| 409 | | |