<?php

/* ********************************************************************
 * PHF-Get - 0.4.0 (beta)
 * Copyright (C) 2022 - GreyWyvern
 *
 * Licenced under the BSD Licence
 *  - https://greywyvern.com/code/bsd.txt
 *
 ******************************************************************* */

class PHF_Component {
  private $purl;
  private $body;
  private $info;

  public $parent = false;
  public $url;
  public $type = '';
  public $forcetype = '';
  public $accept = array();
  public $error;
  public $output;

  public function __construct($url) {
    $this->url = preg_replace('/#.*$/', '', trim($url));

    // Output is the URL until we can swap it for a data: URI
    $this->output = $url;

    if (strpos($url, 'data:') === 0) {


    } else if (preg_match('/^https?:\/\/([^\/?#]*)([^?#]*)(\?([^#]*))?(#(.*))?/', $this->url)) {

      $this->purl = parse_url($url);

    } else {

      $this->error = 'Unsupported URL';

    }
  }

  public function executePHF($depth = 0) {
    global $_PHF;

    if ($this->error) return false;

    // Go back through all parents. If this URL is found, this is a
    // circular reference chain. Break it.
    $parent = $this->parent;
    while ($parent !== false) {
      if ($parent->url == $this->url) {

        return false;
      }
      $parent = $parent->parent;
    } 

    // This URL is already in the cache
    if (isset($_PHF->cache[$this->url])) {

      $this->output = $_PHF->cache[$this->url];
      return false;
    }

    // Bail out if deeper than the depth limit
    if ($depth >= $_PHF->depth_limit) {

      return false;
    }


    $this->getURL();

    // No info was gathered
    if (!$this->info) return false;
    if ($this->info['http_code'] != 200) return false;
    if (!$this->body) return false;

    // If an error occurred, log it for later
    if ($this->error) {

      $_PHF->errors[] = $this->error;
      return false;
    }

    // Find the type of this document
    foreach ($_PHF->types as $mimetype => $extension) {
      if (preg_match('/\b'.preg_quote($mimetype, '/').'\b/', $this->info['content_type'])) {
        $this->type = $mimetype;

        // Restrict to approved mime-types
        if (count($this->accept)) {
          if (!in_array($this->type, $this->accept)) {

            return false;

          } else if ($this->forcetype) {

            $this->type = $this->forcetype;
          }
        }
        break;
      }
    }

    // Search for links in this document depending on type
    switch ($this->type) {
      case 'text/html':
      case 'application/xhtml+xml':

        /* ***** <BASE> *********************************** */
        if (preg_match('/<base\s[^>]*>/i', $this->body, $base)) {
          if (preg_match('/ href=(["\'])(.+?)\1/', $base[0], $url)) {
            $url[2] = $this->createChildURL($url[2]);
            $this->purl = parse_url($url[2]);
          }
          $this->body = str_replace($base[0], '', $this->body);
        }

        /* ***** <FRAMESET> ************************************** */
        if (preg_match('/<frameset[^>]*>(.*?)<\/frameset>/si', $this->body, $frameset)) {
          preg_match_all('/<frame\s[^>]+>/i', $frameset[0], $frames);
          foreach ($frames[0] as $frame) {
            $newframe = $frame;
            if (preg_match('/\ssrc=(["\'])(.+?)\1/', $frame, $url)) {
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
              $data->parent = &$this;
              if ($data->executePHF($depth + 1))
                $newframe = str_replace($url[0], ' src="'.$data->output.'"', $newframe);
            }
            $this->body = str_replace($frame, $newframe, $this->body);
          }
        }

        /* ***** <META> ****************************************** */
        preg_match_all('/<meta\s[^>]*>/i', $this->body, $metas);
        foreach ($metas[0] as $meta) {

        }

        /* ***** <LINK> ****************************************** */
        preg_match_all('/<link\s[^>]+>/i', $this->body, $links);
        foreach ($links[0] as $link) {
          $newlink = $link;
          if (preg_match('/ rel=(["\'])stylesheet\1/', $link)) {
            if (preg_match('/\shref=(["\'])(.+?)\1/', $link, $url)) {
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
              $data->parent = &$this;
              $data->accept = array('text/plain', 'text/css');
              $data->forcetype = 'text/css';
              if ($data->executePHF($depth + 1))
                $newlink = str_replace($url[0], ' href="'.$data->output.'"', $newlink);
            }
          } else if (preg_match('/ rel=(["\'])(shortcut )?icon\1/', $link)) {
            if (preg_match('/\shref=(["\'])(.+?)\1/', $link, $url)) {
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
              $data->parent = &$this;
              $data->accept = array(
                'image/bitmap', 'image/gif', 'image/ico', 'image/icon', 'image/jpeg',
                'image/png', 'image/svg+xml', 'image/tiff', 'image/vnd.microsoft.icon',
                'image/webp' , 'image/x-bitmap', 'image/x-icon'
              );
              if ($data->executePHF($depth + 1))
                $newlink = str_replace($url[0], ' href="'.$data->output.'"', $newlink);
            }
          }
          $this->body = str_replace($link, $newlink, $this->body);
        }

        /* ***** <?xml-stylesheet ?-> **************************** */
        if ($this->type == 'application/xhtml+xml') {
          preg_match_all('/<\?xml-stylesheet\s[^>]+\?'.'>/i', $this->body, $xmls);
          foreach ($xmls[0] as $xml) {
            $newxml = $xml;
            if (preg_match('/\shref=(["\'])(.+?)\1/', $xml, $url)) {
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
              $data->parent = &$this;
              $data->accept = array('text/plain', 'text/css');
              $data->forcetype = 'text/css';
              if ($data->executePHF($depth + 1))
                $newlink = str_replace($url[0], ' href="'.$data->output.'"', $newxml);
            }
            $this->body = str_replace($xml, $newxml, $this->body);
          }
        }

        /* ***** <STYLE> ***************************************** */
        preg_match_all('/<style[^>]*>(.*?)<\/style>/si', $this->body, $styles);
        foreach ($styles[0] as $style) {
          $newstyle = $style;
          preg_match_all('/([\s:])url\(([^#][^\)]+)\)/i', $style, $urls);
          foreach ($urls[2] as $key => $url) {
            $data = new PHF_Component($this->createChildURL($url));
            $data->parent = &$this;
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($urls[0][$key], $urls[1][$key].'url('.$data->output.')', $newstyle);
          }
          preg_match_all('/@import\s+(["\'])(.+?)\1/i', $style, $imports);
          foreach ($imports[2] as $key => $import) {
            $data = new PHF_Component($this->createChildURL($import));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/css');
            $data->forcetype = 'text/css';
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($imports[0][$key], '@import "'.$data->output.'"', $newstyle);
          }
          $this->body = str_replace($style, $newstyle, $this->body);
        }

        /* ***** <____ STYLE=""> ********************************* */
        preg_match_all('/(<[^>]+\sstyle=)(["\'])(.+?)\2/i', $this->body, $styles);
        foreach ($styles[3] as $key => $style) {
          $newstyle = $style;
          preg_match_all('/([\s:])url\(([^#]["\']?)([^\)]+)\2\)/i', $style, $urls);
          foreach ($urls[3] as $key => $url) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url)));
            $data->parent = &$this;
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($urls[0][$key], $urls[1][$key].'url('.$data->output.')', $newstyle);
          }
          preg_match_all('/@import\s+(["\'])(.+?)\1/i', $style, $imports);
          foreach ($imports[2] as $key => $import) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($import)));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/css');
            $data->forcetype = 'text/css';
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($imports[0][$key], '@import "'.$data->output.'"', $newstyle);
          }
          $this->body = str_replace($style, $newstyle, $this->body);
        }

        /* ***** <SCRIPT> **************************************** */
        preg_match_all('/<script\s[^>]+>(\s*<\/script>)?/i', $this->body, $scripts);
        foreach ($scripts[0] as $script) {
          $newscript = $script;
          if (preg_match('/\ssrc=(["\'])(.+?)\1/', $script, $url)) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/javascript', 'application/javascript', 'application/x-javascript');
            $data->forcetype = 'text/javascript';
            if ($data->executePHF($depth + 1))
              $newscript = str_replace($url[0], ' src="'.$data->output.'"', $newscript);
          }
          $this->body = str_replace($script, $newscript, $this->body);
        }

        /* ***** <IFRAME> **************************************** */
        preg_match_all('/<iframe\s[^>]+>/i', $this->body, $iframes);
        foreach ($iframes[0] as $iframe) {
          $newiframe = $iframe;
          if (preg_match('/\ssrc=(["\'])(.+?)\1/', $iframe, $url)) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
            $data->parent = &$this;
            if ($data->executePHF($depth + 1))
              $newiframe = str_replace($url[0], ' src="'.$data->output.'"', $newiframe);
          }
          $this->body = str_replace($iframe, $newiframe, $this->body);
        }

        /* ***** <IMG> ******************************************* */
        preg_match_all('/<img\s[^>]+>/i', $this->body, $imgs);
        foreach ($imgs[0] as $img) {
          $newimg = $img;
          if (preg_match('/\ssrc=(["\'])(.+?)\1/', $img, $url)) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
            $data->parent = &$this;
            $data->accept = array(
              'image/bitmap', 'image/gif', 'image/ico', 'image/icon', 'image/jpeg',
              'image/png', 'image/svg+xml', 'image/tiff', 'image/vnd.microsoft.icon',
              'image/webp' , 'image/x-bitmap', 'image/x-icon'
            );
            if ($data->executePHF($depth + 1))
              $newimg = str_replace($url[0], ' src="'.$data->output.'"', $newimg);
          }
          if (preg_match('/\ssrcset=(["\'])(.+?)\1/', $img, $urlset)) {
            $urls = explode(',', $urlset[2]);
            $newurlset = $urlset[2];
            foreach ($urls as $url) {
              $url = preg_replace('/ [\d.]+[wx]/', '', $url);
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url)));
              $data->parent = &$this;
              $data->accept = array(
                'image/bitmap', 'image/gif', 'image/ico', 'image/icon', 'image/jpeg',
                'image/png', 'image/svg+xml', 'image/tiff', 'image/vnd.microsoft.icon',
                'image/webp' , 'image/x-bitmap', 'image/x-icon'
              );
              if ($data->executePHF($depth + 1))
                $newurlset = str_replace($url, $data->output, $newurlset);
            }
            $newimg = str_replace($urlset[2], $newurlset, $newimg);
          }
          $this->body = str_replace($img, $newimg, $this->body);
        }

        /* ***** <INPUT TYPE="IMAGE"> **************************** */
        preg_match_all('/<input\s[^>]+>/i', $this->body, $inputs);
        foreach ($inputs[0] as $input) {
          $newinput = $input;
          if (preg_match('/ type=(["\'])image\1/', $input)) {
            if (preg_match('/\ssrc=(["\'])(.+?)\1/', $input, $url)) {
              $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
              $data->parent = &$this;
              $data->accept = array(
                'image/bitmap', 'image/gif', 'image/ico', 'image/icon', 'image/jpeg',
                'image/png', 'image/svg+xml', 'image/tiff', 'image/vnd.microsoft.icon',
                'image/webp' , 'image/x-bitmap', 'image/x-icon'
              );
              if ($data->executePHF($depth + 1))
                $newinput = str_replace($url[0], ' src="'.$data->output.'"', $newinput);
            }
            $this->body = str_replace($input, $newinput, $this->body);
          }
        }

        /* ***** <____ BACKGROUND=""> **************************** */
        preg_match_all('/(<[^>]+\sbackground=)(["\'])(.+?)\2/i', $this->body, $backgrounds);
        foreach ($backgrounds[3] as $key => $background) {
          $data = new PHF_Component($this->createChildURL(html_entity_decode($background)));
          $data->parent = &$this;
          $data->accept = array(
            'image/bitmap', 'image/gif', 'image/ico', 'image/icon', 'image/jpeg',
            'image/png', 'image/svg+xml', 'image/tiff', 'image/vnd.microsoft.icon',
            'image/webp' , 'image/x-bitmap', 'image/x-icon'
          );
          if ($data->executePHF($depth + 1))
            $this->body = str_replace($backgrounds[0][$key], $backgrounds[1][$key].'"'.$data->output.'"', $this->body);
        }


        // Add a <BASE> tag that correctly targets links to the original location
        if (preg_match('/<head[^>]*>/i', $this->body, $head))
          $this->body = str_replace($head[0], $head[0]."\n".'<base href="'.$this->url.'">', $this->body);

        break;

      case 'text/xml':
      case 'image/svg+xml':
      case 'application/xml':

        /* ***** <?xml-stylesheet ?-> **************************** */
        preg_match_all('/<\?xml-stylesheet\s[^>]+\?>/i', $this->body, $xmls);
        foreach ($xmls[0] as $xml) {
          $newxml = $xml;
          if (preg_match('/\shref=(["\'])(.+?)\1/', $xml, $url)) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url[2])));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/css');
            $data->forcetype = 'text/css';
            if ($data->executePHF($depth + 1))
              $newlink = str_replace($url[0], ' href="'.$data->output.'"', $newxml);
          }
          $this->body = str_replace($xml, $newxml, $this->body);
        }

        /* ***** <STYLE> ***************************************** */
        preg_match_all('/<style[^>]*>(.*?)<\/style>/si', $this->body, $styles);
        foreach ($styles[0] as $style) {
          $newstyle = $style;
          preg_match_all('/([\s:])url\(([^#][^\)]+)\)/i', $style, $urls);
          foreach ($urls[2] as $key => $url) {
            $data = new PHF_Component($this->createChildURL($url));
            $data->parent = &$this;
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($urls[0][$key], $urls[1][$key].'url('.$data->output.')', $newstyle);
          }
          preg_match_all('/@import\s+(["\'])(.+?)\1/i', $style, $imports);
          foreach ($imports[2] as $key => $import) {
            $data = new PHF_Component($this->createChildURL($import));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/css');
            $data->forcetype = 'text/css';
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($imports[0][$key], '@import "'.$data->output.'"', $newstyle);
          }
          $this->body = str_replace($style, $newstyle, $this->body);
        }

        /* ***** <____ STYLE=""> ********************************* */
        preg_match_all('/(<[^>]+\sstyle=)(["\'])(.+?)\2/i', $this->body, $styles);
        foreach ($styles[3] as $key => $style) {
          $newstyle = $style;
          preg_match_all('/([\s:])url\(([^#]["\']?)([^\)]+)\2\)/i', $style, $urls);
          foreach ($urls[3] as $key => $url) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($url)));
            $data->parent = &$this;
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($urls[0][$key], $urls[1][$key].'url('.$data->output.')', $newstyle);
          }
          preg_match_all('/@import\s+(["\'])(.+?)\1/i', $style, $imports);
          foreach ($imports[2] as $key => $import) {
            $data = new PHF_Component($this->createChildURL(html_entity_decode($import)));
            $data->parent = &$this;
            $data->accept = array('text/plain', 'text/css');
            $data->forcetype = 'text/css';
            if ($data->executePHF($depth + 1))
              $newstyle = str_replace($imports[0][$key], '@import "'.$data->output.'"', $newstyle);
          }
          $this->body = str_replace($style, $newstyle, $this->body);
        }
        break;

      case 'text/css':

        /* ***** url() ******************************************* */
        preg_match_all('/([\s:])url\(([^#]["\']?)([^\)]+)\2\)/i', $this->body, $urls);
        foreach ($urls[3] as $key => $url) {
          $data = new PHF_Component($this->createChildURL($url));
          $data->parent = &$this;
          if ($data->executePHF($depth + 1))
            $this->body = str_replace($urls[0][$key], $urls[1][$key].'url('.$data->output.')', $this->body);
        }

        /* ***** @import "" ************************************** */
        preg_match_all('/@import\s+(["\'])(.+?)\1/i', $this->body, $imports);
        foreach ($imports[2] as $key => $import) {
          $data = new PHF_Component($this->createChildURL($import));
          $data->parent = &$this;
          $data->accept = array('text/plain', 'text/css');
          $data->forcetype = 'text/css';
          if ($data->executePHF($depth + 1))
            $this->body = str_replace($imports[0][$key], '@import "'.$data->output.'"', $this->body);
        }
        break;

      case 'text/javascript':
      case 'application/javascript':
      case 'application/x-javascript':
        // No parsing for links in javascript
        break;

      case '':
        // Unknown mime-type, should log this



        return false;

      default:
        // Binary and other no-parse formats

    }

    if ($depth) {
      if ($this->type == 'text/css') {
        $this->body = str_replace(array("\n", "\r"), ' ', $this->body);
        $this->body = preg_replace('/ {2,}/', ' ', $this->body);
        $this->output = 'data:'.$this->type.','.htmlspecialchars(rawurlencode($this->body));
      } else $this->output = 'data:'.$this->type.';base64,'.base64_encode($this->body);


      $_PHF->cache[$this->url] = $this->output;

    // Export top level data as-is without encoding
    } else {
      $this->output = $this->body;


    }
    return true;
  }

  /* ******************************************************************
   * Take a relative or absolute URL and return an absolute URL based
   * on the current working directory
   */
  private function createChildURL($url) {
    $url = trim($url);

    if (!$url || strpos($url, 'data:') === 0) {
      $child = $url;

    // Relative to scheme
    } else if (strpos($url, '//') === 0) {
      $child = $this->purl['scheme'].':'.$url;

    // Relative to root
    } else if (strpos($url, '/') === 0) {
      $child = $this->purl['scheme'].'://'.$this->purl['host'].((isset($this->purl['port'])) ? ':'.$this->purl['port'] : '').$url;

    // Relative to current directory
    } else if (!preg_match('/^http/', $url)) {
      if (!isset($this->purl['path'])) $this->purl['path'] = '';
      $path = preg_replace('/\/[^\/]*$/', '/', $this->purl['path']);
      $child = $this->purl['scheme'].'://'.$this->purl['host'].((isset($this->purl['port'])) ? ':'.$this->purl['port'] : '').$path.$url;

    // Absolute URL
    } else $child = $url;

    return $child;
  }

  /* ******************************************************************
   * Use cURL to fetch a URL and account for any redirects or errors
   */
  private function getURL() {
    global $_PHF;

    if (strpos($this->url, 'data:') === 0) {

      $data = explode(',', preg_replace('/^data:/', '', $this->url), 2);

      if (isset($data[1])) {
        if (preg_match('/([a-z]+\/[a-z0-9.+_-]+)(;[a-z]+=[^,]+)?(;base64)?/i', $data[0], $mime)) {
          if (in_array($mime[1], array('text/html', 'application/xhtml+xml', 'text/xml', 'image/svg+xml', 'application/xml', 'text/css'))) {
            $this->body = base64_decode(urldecode($data[1]));
            $this->purl = $this->parent->purl;
            $this->info = array(
              'http_code' => 200,
              'redirect_url' => '',
              'content_type' => trim($data[0])
            );

            return;
          }
        }
      }

    } else {

      $loop = 0;
      $testurl = $this->url;

      do {
        $ch = curl_init($testurl);

        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_USERAGENT, $_PHF->user_agent);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // curl_setopt($ch, CURLOPT_MAXFILESIZE, $_PHF->max_size);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); 
        // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        // curl_setopt($ch, CURLOPT_MAXREDIRS, $_PHF->redirect_limit);
        curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
        curl_setopt($ch, CURLOPT_NOPROGRESS, false);
        curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($ch, $dls, $dl, $uls, $ul) {
          global $_PHF;

          if ($dls > $_PHF->max_size || $dl > $_PHF->max_size) return 1;

          $info = curl_getinfo($ch);
          if ($info['redirect_url']) return 1;
          if ($info['http_code'] && $info['http_code'] >= 400) return 1;

          return 0;
        });

        $body = curl_exec($ch);
        $this->error = curl_error($ch);
        $this->info = curl_getinfo($ch);

        while (strpos($body, "\x1f\x8b") === 0)
          $body = gzinflate(substr($body, 10));

        curl_close($ch);

        if ($this->info['redirect_url']) {
          if (++$loop > $_PHF->redirect_limit) {

            $this->error = 'Redirect loop encountered at: '.$this->url;
            return;
          }
          $testurl = $this->createChildURL($this->info['redirect_url']);

        } else if ($this->info['http_code'] != 200) {

          $this->error = 'HTTP error: ('.$this->info['http_code'].')';


          return;

        } else if ($this->error == 'Callback aborted') {

          $this->error = 'Maximum file size exceeded at URL: '.$this->url;
          return;

        } else if ($this->error) {

          return;
        }

      } while ($this->info['redirect_url']);

      if ($testurl != $this->url) {

        $this->url = $testurl;
        $this->purl = parse_url($this->url);
      }


      $this->body = $body;
    }
  }
}


class PHF_Get extends PHF_Component {
  private $version = '0.4.0 (beta)';

  public $user_agent = '';
  public $max_size = 524288;
  public $depth_limit = 10;
  public $redirect_limit = 10;
  public $errors = array();
  public $cache = array();
  public $types = array(
    'application/font-woff' => 'woff',
    'application/javascript' => 'js',
    'application/octet-stream' => '',
    'application/font-sfnt' => 'sfnt',
    'application/vnd.ms-fontobject'  => 'eot',
    'application/x-font-ttf' => 'ttf',
    'application/x-javascript' => 'js',
    'application/xhtml+xml' => 'xhtml',
    'application/xml' => 'xml',
    'font/otf' => 'otf',
    'font/sfnt' => 'sfnt',
    'font/ttf' => 'ttf',
    'font/woff' => 'woff',
    'font/woff2' => 'woff2',
    'image/bitmap' => 'bmp',
    'image/gif' => 'gif',
    'image/ico' => 'ico',
    'image/icon' => 'ico',
    'image/jpeg' => 'jpg',
    'image/png' => 'png',
    'image/svg+xml' => 'svg',
    'image/tiff' => 'tif',
    'image/vnd.microsoft.icon' => 'ico',
    'image/webp' => 'webp',
    'image/x-bitmap' => 'bmp',
    'image/x-icon' => 'ico',
    'text/css' => 'css',
    'text/html' => 'html',
    'text/javascript' => 'js',
    'text/plain' => 'txt',
    'text/xml' => 'xml'
  );

  public function __construct($url) {
    parent::__construct($url);

    $this->user_agent = 'phf_get '.$this->version;
  }
}


// ***** Usage
$_PHF = new PHF_Get('http://example.com');
$_PHF->user_agent = $_SERVER['HTTP_USER_AGENT'];
$_PHF->executePHF();

// Contents of document are now in $_PHF->output
echo $_PHF->output;

// Any errors encountered are in the $_PHF->errors array
var_dump($_PHF->errors);

?>