yf_site_map user module

Site Map Generator (implementation of the http://www.sitemaps.org/protocol.php).

Fileplugins/site_map/modules/yf_site_map.class.php
Usemodule('site_map'), URL ?object=site_map

Public properties

A site sets them in its config with $PROJECT_CONF['site_map']['NAME'] = ...;

PropertyDefaultDescription
$ALLOW_REWRITE
bool
false Allow rewrite
$CHANGEFREQ_VALUES
array
['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', ... (7 items)]
array (
  0 => 'always',
  1 => 'hourly',
  2 => 'daily',
  3 => 'weekly',
  4 => 'monthly',
  5 => 'yearly',
  6 => 'never',
)
Frequency avail values @conf_skip
$DEFAULT_CHANGEFREQ
mixed
'daily'
$DEFAULT_LAST_UPDATE
mixed
''
$DEFAULT_PRIORITY
mixed
''
$DIRECT_FILE_OUTPUT
bool
true
$HOOK_NAMES
@conf_skip
['_hook_sitemap', '_hook_site_map', '_site_map_items']
$LIMIT_REACHED
bool
false Do not change! It's a flag which is become 'true' if number of sitemap files reached. There will be no actions after this
$LOCK_FILE_NAME
string
'site_map.lock' Lock file name
$LOCK_TIMEOUT
int
600 Lock timeout
$MAX_ENTRIES
int
50000 Max entries for sitemap file
$MAX_SITEMAPS
int
1000 Max number of sitemaps
$MAX_SIZE
int
10000000 Max sitemap filesize
$MAX_URL_LENGTH
int
2048 Limit max URL length to the 2048 symbols
$MODULES_TO_INCLUDE
array
[] Array of site modules to include their parts of sitemap
$NOTIFY_GOOGLE
bool
false Notify Google
$REWRITE_SPLIT_LENGTH
int
200000 Rewrite split length
$SECURITY_URL_PARAM
bool
''
$SITEMAP_FILE_NAME
string
'site_map' Sitemap file name
$SITEMAP_LIVE_TIME
int
43200 Site map TTL
$SITEMAP_STORE_FOLDER
string
'site_map/' Sitemap store folder
$SITEMAP_WEB_PATH ''
$SITE_MAP_ENABLED
bool
false Enable\disable site map generation
$TEST_MODE
bool
false
$USE_LOCKING
bool
false Use locking
$_entries_counter null
$_file_extension
string
'.xml' @conf_skip
$_file_for_google null
$_file_name
string
'' @conf_skip
$_fp null
$_notify_url
string
'' @conf_skip
$_path_size_bytes null
$_sitemap_file_counter null
$_total_length null

Public methods 16

show

public function show()

Default method.

    public function show()
    {
        if ($this->SECURITY_URL_PARAM && ($_GET['id'] ?? null) != $this->SECURITY_URL_PARAM) {
            main()->NO_GRAPHICS = true;
            return print _e('Wrong url');
        }
        return $this->_generate_sitemap();
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $arguments)

Catch missing method call.

    public function __call($name, $arguments)
    {
        trigger_error(__CLASS__ . ': No method ' . $name, E_USER_WARNING);
        return false;
    }

_add

public function _add($data = [])

Alias for _store_item.

    public function _add($data = [])
    {
        return $this->_store_item($data);
    }
public function _do_notify_google()

Notify google about update. $this->_file_for_google - sitemap or sitemap index file to give to google.

    public function _do_notify_google()
    {
        if ($this->NOTIFY_GOOGLE) {
            fopen('http://www.google.com/webmasters/sitemaps/ping?sitemap=' . urlencode($this->_notify_url), 'r');
        }
    }
public function _generate_sitemap()

Sitemap format generator (compatible with Google, Yahoo, MSN etc).

https://www.google.com/webmasters/sitemaps/docs/en/about.html
XML-Specification: https://www.google.com/webmasters/sitemaps/docs/en/protocol.html

You can use the gzip feature or compress your Sitemap files using gzip.
Please note that your uncompressed Sitemap file may not be larger than 10MB.

No support for more the 50.000 URLs at the moment. See
http://www.google.com/webmasters/sitemaps/docs/en/protocol.html#sitemapFileRequirements

    public function _generate_sitemap()
    {
        main()->NO_GRAPHICS = true;
        if ( ! $this->SITE_MAP_ENABLED) {
            return false;
        }
        // !! Important !!
        // Turn off rewrite adding url params into sitemap urls
        _class('rewrite')->URL_ADD_BUILTIN_PARAMS = false;

        $_sitemap_base_name = $this->SITEMAP_FILE_NAME;
        // Check if needed to recreate sitemap files
        $_path = $this->SITEMAP_STORE_FOLDER . $this->SITEMAP_FILE_NAME . '_index' . $this->_file_extension;
        if (file_exists($_path) && (filemtime($_path) + $this->SITEMAP_LIVE_TIME) > time() && ! $this->TEST_MODE) {
            // send headers
            $this->_redirect_sitemap($this->SITEMAP_WEB_PATH . $this->SITEMAP_FILE_NAME . '_index' . $this->_file_extension);
            return false;
        }
        if ( ! file_exists($_path)) {
            $_path = $this->SITEMAP_STORE_FOLDER . $this->SITEMAP_FILE_NAME . '1' . $this->_file_extension;
            if (file_exists($_path) && (filemtime($_path) + $this->SITEMAP_LIVE_TIME) > time() && ! $this->TEST_MODE) {
                // send headers
                $this->_redirect_sitemap($this->SITEMAP_WEB_PATH . $this->SITEMAP_FILE_NAME . '1' . $this->_file_extension);
                return false;
            }
        }
        // Start generate sitemap
        @ignore_user_abort(true);
        @set_time_limit($this->LOCK_TIMEOUT);

        if ($this->USE_LOCKING) {
            clearstatcache();
            if (file_exists($this->LOCK_FILE_NAME)) {
                if ((time() - filemtime($this->LOCK_FILE_NAME)) > $this->LOCK_TIMEOUT) {
                    unlink($this->LOCK_FILE_NAME);
                } else {
                    return false;
                }
            }
            file_put_contents($this->LOCK_FILE_NAME, time());
        }
        $this->_sitemap_file_counter = 1;
        _mkdir_m($this->SITEMAP_STORE_FOLDER);
        _class('dir')->delete_files($this->SITEMAP_STORE_FOLDER, '/' . $_sitemap_base_name . '.*/i');

        $this->_fp = fopen($this->SITEMAP_STORE_FOLDER . $this->SITEMAP_FILE_NAME . $this->_sitemap_file_counter . $this->_file_extension, 'w+');
        $this->_total_length = 0;
        $this->_entries_counter = 0;

        $this->_output($this->_tpl_sitemap_header());
        $this->_total_length = strlen($this->_tpl_sitemap_header());

        foreach ((array) $this->MODULES_TO_INCLUDE as $module_name) {
            $module_obj = module_safe($module_name);
            if ($this->LIMIT_REACHED || ! is_object($module_obj)) {
                continue;
            }
            $hook_name = '';
            foreach ((array) $this->HOOK_NAMES as $_hook_name) {
                if (method_exists($module_obj, $_hook_name)) {
                    $hook_name = $_hook_name;
                    break;
                }
            }
            if ($hook_name) {
                $items = $module_obj->$hook_name($this);
                if (is_array($items)) {
                    foreach ((array) $items as $item) {
                        $this->_add($item);
                    }
                }
            }
        }
        if ( ! $this->LIMIT_REACHED) {
            $this->_output($this->_tpl_sitemap_footer());
            @fclose($this->_fp);
        }
        $files = _class('dir')->scan($this->SITEMAP_STORE_FOLDER);
        $sitemaps = null;
        foreach ((array) $files as $file_name) {
            if (false !== strpos($file_name, '.svn') || false !== strpos($file_name, '.git')) {
                continue;
            }
            $path_info = pathinfo($file_name);
            $_sitemap_filename_pattern = '/^(' . $this->SITEMAP_FILE_NAME . ')([0-9]{1,3})(\.xml)$/';
            if ( ! preg_match($_sitemap_filename_pattern, $path_info['basename'])) {
                continue;
            }
            $sitemaps[$file_name] = $file_name;
        }
        if ($sitemaps) {
            foreach ((array) $sitemaps as $filename) {
                $this->_process_sitemap_file($filename);
            }
        }
        $this->_file_for_google = str_replace(PROJECT_PATH, WEB_PATH, $this->SITEMAP_STORE_FOLDER) . $this->SITEMAP_FILE_NAME . '1' . $this->_file_extension;
        if ($sitemaps && count((array) $sitemaps) > 1) {
            $this->_output($this->_tpl_sitemap_index_header());

            // Create sitemap index
            $this->_fp = fopen($this->SITEMAP_STORE_FOLDER . $this->SITEMAP_FILE_NAME . '_index' . $this->_file_extension, 'w+');
            foreach ((array) $sitemaps as $sitemap_file) {
                $string = "\t<sitemap>\n";
                $string .= "\t\t<loc>" . str_replace(PROJECT_PATH, WEB_PATH, $sitemap_file) . "</loc>\n";
                $last_mod = $this->_iso8601_date(filemtime($sitemap_file));
                if ($last_mod) {
                    $string .= "\t\t<lastmod>" . $last_mod . "</lastmod>\n";
                }
                $string .= "\t</sitemap>\n";
                // Store entry data to file
                $this->_output($string);
            }
            fclose($this->_fp);

            $this->_output($this->_tpl_sitemap_index_footer());

            $this->_file_for_google = str_replace(PROJECT_PATH, WEB_PATH, $this->SITEMAP_STORE_FOLDER) . $this->SITEMAP_FILE_NAME . '_index' . $this->_file_extension;
        }
        // Release lock
        if ($this->USE_LOCKING) {
            unlink($this->LOCK_FILE_NAME);
        }
        $this->_redirect_sitemap($this->_file_for_google);

        $this->_do_notify_google();
    }
public function _get_modules_from_files()

Get available user modules from the project modules folder.

    public function _get_modules_from_files()
    {
        $regex = '~function(\s)+(' . implode('|', $this->HOOK_NAMES) . ')\s*\(~ims';

        $yf_prefix_len = strlen(YF_PREFIX);
        $yf_cls_ext_len = strlen(YF_CLS_EXT);
        $site_prefix_len = strlen(YF_SITE_CLS_PREFIX);

        $pattern = USER_MODULES_DIR . '*' . YF_CLS_EXT;
        $places = [
            'yf_main' => YF_PATH . $pattern,
            'yf_plugins' => YF_PATH . 'plugins/*/' . $pattern,
            'project_main' => PROJECT_PATH . $pattern,
            'project_plugins' => PROJECT_PATH . 'plugins/*/' . $pattern,
            'app_main' => APP_PATH . $pattern,
            'app_plugins' => APP_PATH . 'plugins/*/' . $pattern,
        ];
        $modules = [];
        foreach ($places as $place_name => $glob) {
            foreach (glob($glob) as $path) {
                if (substr($path, -$yf_cls_ext_len) !== YF_CLS_EXT) {
                    continue;
                }
                $name = substr(basename($path), 0, -$yf_cls_ext_len);
                if (substr($name, 0, $yf_prefix_len) === YF_PREFIX) {
                    $name = substr($name, $yf_prefix_len);
                }
                if (substr($name, 0, $site_prefix_len) === YF_SITE_CLS_PREFIX) {
                    $name = substr($name, $site_prefix_len);
                }
                if ( ! strlen($name) || ! preg_match($regex, file_get_contents($path), $m)) {
                    continue;
                }
                $modules[$name] = $name;
            }
        }
        ksort($modules);
        return $modules;
    }

_init

public function _init()

YF module constructor.

    public function _init()
    {
        if ($this->USE_LOCKING) {
            $this->LOCK_FILE_NAME = STORAGE_PATH . $this->LOCK_FILE_NAME;
        }
        $this->SITEMAP_WEB_PATH = WEB_PATH . 'uploads/' . $this->SITEMAP_STORE_FOLDER;
        $this->SITEMAP_STORE_FOLDER = PROJECT_PATH . 'uploads/' . $this->SITEMAP_STORE_FOLDER;

        // Calculate size of a string (web path) adding to url when file processed
        $path = mb_convert_encoding(WEB_PATH, 'UTF-8', 'ISO-8859-1');
        $this->_path_size_bytes = strlen(htmlspecialchars($path));
        // Leave some space in file size
        $this->MAX_SIZE = $this->MAX_SIZE * 0.9;

        $this->_notify_url = url('/@object');

        if (empty($this->MODULES_TO_INCLUDE)) {
            $this->MODULES_TO_INCLUDE = $this->_get_modules_from_files();
        }
        // Ensure uniqueness of module names
        $tmp = [];
        foreach ((array) $this->MODULES_TO_INCLUDE as $v) {
            $tmp[$v] = $v;
        }
        $this->MODULES_TO_INCLUDE = $tmp;

        // Remove non-active modules
        $active_modules = db()->select('name', 'name AS n2')->from('user_modules')->where('active = 1')->get_2d();
        foreach ($this->MODULES_TO_INCLUDE as $k => $name) {
            if ( ! isset($active_modules[$name])) {
                unset($this->MODULES_TO_INCLUDE[$k]);
            }
        }
        if ($this->ALLOW_REWRITE && ! tpl()->REWRITE_MODE) {
            $this->ALLOW_REWRITE = false;
        }
        $this->_file_extension = '_' . conf('language') . $this->_file_extension;
    }

_iso8601_date

public function _iso8601_date($timestamp)

backwards compatible for PHP version < 5.

    public function _iso8601_date($timestamp)
    {
        return date('c', $timestamp);
    }

_output

public function _output($string)

Output generated content.

    public function _output($string)
    {
        if ($this->TEST_MODE) {
            if (DEBUG_MODE) {
                header('Content-type: text/html');
                echo '<pre>' . _prepare_html($string) . '</pre>';
            } else {
                header('Content-type: text/xml');
                echo $string;
            }
            return false;
        }
        if ($this->_fp) {
            fwrite($this->_fp, $string);
        }
    }
public function _process_sitemap_file($_path = '')

Processes sitemap file. Escapes special chars. Makes full webpath.

    public function _process_sitemap_file($_path = '')
    {
        $rewrited = null;
        $text = file_get_contents($_path);
        if ($this->ALLOW_REWRITE) {
            $RW = _class('rewrite');
            $RW->FORCE_NO_DEBUG = true;
            // Save old pattern
            $old_pattern = $RW->_links_pattern;
            // Aplly our custom pattern
            $RW->_links_pattern = '/(<loc>)([^\<]+)(<\/loc>)/ms';
            $rewrited = null;
            foreach ((array) common()->_my_split($text, '<url>', $this->REWRITE_SPLIT_LENGTH) as $_cur_text) {
                $rewrited .= $RW->_rewrite_replace_links($_cur_text);
            }
            $text = $rewrited;
            unset($rewrited);
            // Revert old links pattern
            $RW->_links_pattern = $old_pattern;
        } else {
            $text = str_replace('<loc>./?', '<loc>' . WEB_PATH . '?', $text);
        }
        $text = preg_replace_callback('/<loc>([^\<]+)<\/loc>/ims', function ($m) {
            return '<loc>' . htmlspecialchars($m[1]) . '</loc>';
        }, $text);
        file_put_contents($_path, $text);
    }
public function _redirect_sitemap($location)

Redirect to sitemap source files.

    public function _redirect_sitemap($location)
    {
        if ($this->TEST_MODE) {
            return false;
        }
        if ($this->DIRECT_FILE_OUTPUT) {
            header('Content-type: text/xml');
            readfile(str_replace($this->SITEMAP_WEB_PATH, $this->SITEMAP_STORE_FOLDER, $location));
            exit();
        }
        header((($_SERVER['SERVER_PROTOCOL'] ?? null) ? ($_SERVER['SERVER_PROTOCOL'] ?? null) : 'HTTP/1.1') . ' 301 Moved Permanently');
        header('Location: ' . $location);
    }

_store_item

public function _store_item($data = [])

Store sitemap item.

    public function _store_item($data = [])
    {
        $string = null;
        if (is_string($data) && strlen($data)) {
            $data = ['url' => $data];
        }
        if (empty($data) || empty($data['url'])) {
            return false;
        }
        if ($this->LIMIT_REACHED) {
            return false;
        }
        // Shortcut for calling url('/some_module')
        if (substr($data['url'], 0, 1) === '/' && substr($data['url'], 0, 2) !== '//') {
            $data['url'] = url($data['url']);
        }
        $location = $data['url'];
        if ((strlen($location) + $this->_path_size_bytes) >= $this->MAX_URL_LENGTH) {
            return false;
        }
        if ( ! $data['last_update'] && $this->DEFAULT_LAST_UPDATE) {
            $data['last_update'] = $this->DEFAULT_LAST_UPDATE;
            if (is_callable($data['last_update'])) {
                $func = $data['last_update'];
                $data['last_update'] = $func($data);
            }
        }
        if ( ! $data['priority'] && $this->DEFAULT_PRIORITY) {
            $data['priority'] = $this->DEFAULT_PRIORITY;
            if (is_callable($data['priority'])) {
                $func = $data['priority'];
                $data['priority'] = $func($data);
            }
        }
        if ( ! $data['changefreq'] && $this->DEFAULT_CHANGEFREQ) {
            $data['changefreq'] = $this->DEFAULT_CHANGEFREQ;
            if (is_callable($data['changefreq'])) {
                $func = $data['changefreq'];
                $data['changefreq'] = $func($data);
            }
        }

        $string = "\t<url>\n";
        $string .= "\t\t<loc>" . $location . "</loc>\n";
        // Adding size of path string to a total size of data
        $this->_total_length += $this->_path_size_bytes;
        if ($data['last_update']) {
            $string .= "\t\t<lastmod>" . $this->_iso8601_date($data['last_update']) . "</lastmod>\n";
        }
        if ($data['priority']) {
            $string .= "\t\t<priority>" . (float) ($data['priority']) . "</priority>\n";
        }
        if ($data['changefreq'] && in_array($data['changefreq'], $this->CHANGEFREQ_VALUES)) {
            $string .= "\t\t<changefreq>" . $data['changefreq'] . "</changefreq>\n";
        }
        $string .= "\t</url>\n";

        $this->_total_length += strlen($string);
        $this->_entries_counter++;
        // Check total length and number of entries before save
        if ($this->_total_length >= $this->MAX_SIZE || $this->_entries_counter >= $this->MAX_ENTRIES) {
            // Finish to write file and create another one
            if ($this->_fp) {
                $this->_output($this->_tpl_sitemap_footer());
                fclose($this->_fp);
            }
            if ($this->_entries_counter >= $this->MAX_ENTRIES || $this->_total_length >= $this->MAX_SIZE) {
                $this->_entries_counter = 0;
                $this->_total_length = 0;
            }
            // Check if we reached max sitemap files number
            if ($this->_sitemap_file_counter >= $this->MAX_SITEMAPS) {
                $this->LIMIT_REACHED = true;
            }
            if ( ! $this->LIMIT_REACHED) {
                $this->_sitemap_file_counter++;
                // Create and open to write next sitemap file
                $this->_fp = fopen($this->SITEMAP_STORE_FOLDER . $this->SITEMAP_FILE_NAME . $this->_sitemap_file_counter . $this->_file_extension, 'w+');

                $this->_output($this->_tpl_sitemap_header());
                $this->_total_length = strlen($this->_tpl_sitemap_header());
            }
        }
        if ( ! $this->LIMIT_REACHED) {
            $this->_output($string);
        }
    }
public function _tpl_sitemap_header()
    public function _tpl_sitemap_header()
    {
        return '<?xml version="1.0" encoding="UTF-8"?>'
            . PHP_EOL . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
            . PHP_EOL . ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">'
            . PHP_EOL;
    }
public function _tpl_sitemap_index_header()
    public function _tpl_sitemap_index_header()
    {
        return '<?xml version="1.0" encoding="UTF-8"?>'
            . PHP_EOL . '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
            . PHP_EOL . ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd">'
            . PHP_EOL;
    }