├── .editorconfig ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── routes.php ├── phpunit.xml.dist ├── src ├── Controller │ ├── AppController.php │ └── SitemapsController.php ├── Lib │ └── Iterators │ │ ├── ExtFilteredDirIterator.php │ │ └── PagesIterator.php ├── Model │ └── Behavior │ │ └── SitemapBehavior.php └── Template │ └── Sitemaps │ ├── index.ctp │ └── xml │ └── index.ctp ├── tests ├── App │ └── Template │ │ └── Layout │ │ └── default.ctp ├── Fixture │ └── PagesFixture.php ├── TestCase │ ├── Controller │ │ └── SitemapsControllerTest.php │ └── Model │ │ └── Behavior │ │ └── SitemapBehaviorTest.php ├── bootstrap.php └── config │ └── routes.php └── webroot └── empty /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | # Tab indentation (no size specified) 9 | [{*.js,*.php,*.ctp}] 10 | indent_style = tab 11 | 12 | # Matches the exact files package.json and .travis.yml 13 | [{package.json,.travis.yml,composer.json,*.yaml,*.yml}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | /composer.lock 3 | 4 | # CakePHP 3 5 | /vendor/* 6 | /tmp/* 7 | /logs/* 8 | 9 | # CakePHP 2 10 | /app/tmp/* 11 | /vendors/* 12 | 13 | # OS 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | Icon? 18 | ._* 19 | .Spotlight-V100 20 | .Trashes 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | 10 | # Environment Variables to set 11 | env: 12 | global: 13 | 14 | 15 | # Cache the composer directories, only allowed if using the container based setup 16 | # which depends on setting sudo to false 17 | cache: 18 | directories: 19 | - $HOME/.composer/cache 20 | 21 | # Branches to be built or not 22 | branches: 23 | # Blacklist these branches 24 | except: 25 | - gh-pages 26 | 27 | before_install: 28 | - composer self-update 29 | - mkdir -p build/logs 30 | 31 | install: 32 | - composer install --no-interaction 33 | 34 | before_script: 35 | - phpenv rehash 36 | - vendor/bin/phpcs --config-set installed_paths vendor/loadsys/loadsys_codesniffer,vendor/cakephp/cakephp-codesniffer 37 | 38 | script: 39 | - vendor/bin/phpcs -np --extensions=php --standard=Loadsys ./src ./tests 40 | - vendor/bin/phpunit --coverage-clover=build/logs/clover.xml 41 | 42 | after_script: 43 | - php vendor/bin/coveralls -v 44 | 45 | notifications: 46 | email: false 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at conduct@loadsys.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Loadsys Web Strategies 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CakePHP-Sitemap 2 | 3 | [![Latest Version](https://img.shields.io/github/release/loadsys/CakePHP-Sitemap.svg?style=flat-square)](https://github.com/loadsys/CakePHP-Sitemap/releases) 4 | [![Build Status](https://img.shields.io/travis/loadsys/CakePHP-Sitemap/master.svg?style=flat-square)](https://travis-ci.org/loadsys/CakePHP-Sitemap) 5 | [![Coverage Status](https://img.shields.io/coveralls/loadsys/CakePHP-Sitemap/master.svg?style=flat-square)](https://coveralls.io/r/loadsys/CakePHP-Sitemap) 6 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/loadsys/cakephp_sitemap.svg?style=flat-square)](https://packagist.org/packages/loadsys/cakephp_sitemap) 8 | 9 | The Sitemap provides a mechanism for displaying Sitemap style information (the url, change frequency, priority and last modified datetime) for a set of Tables that CakePHP has access to. 10 | 11 | 12 | ## Requirements 13 | 14 | * CakePHP 3.0.0+ 15 | * PHP 5.6+ 16 | 17 | 18 | ## Installation 19 | 20 | ```shell 21 | $ composer require loadsys/cakephp_sitemap 22 | ``` 23 | 24 | In your `config/bootstrap.php` file, add: 25 | 26 | ```php 27 | Plugin::load('Sitemap', ['bootstrap' => false, 'routes' => true]); 28 | ``` 29 | 30 | OR 31 | 32 | ```shell 33 | $ bin/cake plugin load Sitemap -r 34 | ``` 35 | 36 | ## Usage 37 | 38 | * Add list of tables to display Sitemap records via an array at `Sitemap.tables` 39 | 40 | ```php 41 | Configure::write('Sitemap.tables', [ 42 | 'Pages', 43 | 'Sites', 44 | 'Camps', 45 | ]); 46 | ``` 47 | 48 | * Add the `Sitemap.Sitemap` Behavior to each table as well 49 | 50 | ```php 51 | $this->addBehavior('Sitemap.Sitemap'); 52 | ``` 53 | 54 | You can now access the sitemap at `/sitemap.xml`. 55 | 56 | ### Configuration 57 | 58 | * Default configuration options for the `Sitemap` Behavior is: 59 | 60 | ```php 61 | 'cacheConfigKey' => 'default', 62 | 'lastmod' => 'modified', 63 | 'changefreq' => 'daily', 64 | 'priority' => '0.9', 65 | 'conditions' => [], 66 | 'order' => [], 67 | 'fields' => [], 68 | 'implementedMethods' => [ 69 | 'getUrl' => 'returnUrlForEntity', 70 | ], 71 | 'implementedFinders' => [ 72 | 'forSitemap' => 'findSitemapRecords', 73 | ], 74 | ``` 75 | 76 | * To modify these options for instance to change the `changefreq` when listing records, update the `addBehavior` method call for the `Table` in question like so: 77 | 78 | ```php 79 | $this->addBehavior('Sitemap.Sitemap', ['changefreq' => 'weekly']); 80 | ``` 81 | 82 | * To customize the url generated for each record create a method named `getUrl` in the matching `Table` class. 83 | 84 | ```php 85 | public function getUrl(\Cake\ORM\Entity $entity) { 86 | return \Cake\Routing\Router::url( 87 | [ 88 | 'prefix' => false, 89 | 'plugin' => false, 90 | 'controller' => $this->registryAlias(), 91 | 'action' => 'display', 92 | $entity->display_id, 93 | ], 94 | true 95 | ); 96 | } 97 | ``` 98 | 99 | * To customize the templates used when displaying the Sitemap, the CakePHP Book provides information regarding [overriding Plugin Templates](http://book.cakephp.org/3.0/en/plugins.html#overriding-plugin-templates-from-inside-your-application). 100 | 101 | ## Contributing 102 | 103 | ### Code of Conduct 104 | 105 | This project has adopted the Contributor Covenant as its [code of conduct](CODE_OF_CONDUCT.md). All contributors are expected to adhere to this code. [Translations are available](http://contributor-covenant.org/). 106 | 107 | ### Reporting Issues 108 | 109 | Please use [GitHub Isuses](https://github.com/loadsys/CakePHP-Sitemap/issues) for listing any known defects or issues. 110 | 111 | ### Development 112 | 113 | When developing this plugin, please fork and issue a PR for any new development. 114 | 115 | Set up a working copy: 116 | ```shell 117 | $ git clone git@github.com:YOUR_USERNAME/CakePHP-Sitemap.git 118 | $ cd CakePHP-Sitemap/ 119 | $ composer install 120 | $ vendor/bin/phpcs --config-set installed_paths vendor/loadsys/loadsys_codesniffer,vendor/cakephp/cakephp-codesniffer 121 | ``` 122 | 123 | Make your changes: 124 | ```shell 125 | $ git checkout -b your-topic-branch 126 | # (Make your changes. Write some tests.) 127 | $ vendor/bin/phpunit 128 | $ vendor/bin/phpcs -p --extensions=php --standard=Loadsys ./src ./tests 129 | ``` 130 | 131 | Then commit and push your changes to your fork, and open a pull request. 132 | 133 | 134 | ## License 135 | 136 | [MIT](https://github.com/loadsys/CakePHP-Sitemap/blob/master/LICENSE.md) 137 | 138 | 139 | ## Copyright 140 | 141 | [Loadsys Web Strategies](http://www.loadsys.com) 2016 142 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loadsys/cakephp_sitemap", 3 | "description": "A CakePHP Plugin for adding automatic XML and HTML Sitemaps to an app", 4 | "type": "cakephp-plugin", 5 | "keywords": [ 6 | "cakephp", 7 | "sitemap" 8 | ], 9 | "license": "MIT", 10 | "homepage": "https://github.com/loadsys/CakePHP-Sitemap", 11 | "authors": [ 12 | { 13 | "name": "Justin Yost", 14 | "homepage": "https://github.com/jtyost2", 15 | "email": "justin@loadsys.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "support": { 20 | "issues": "https://github.com/loadsys/CakePHP-Sitemap/issues", 21 | "source": "https://github.com/loadsys/CakePHP-Sitemap" 22 | }, 23 | "require": { 24 | "php": ">=5.6.0", 25 | "cakephp/cakephp": "~3.1", 26 | "composer/installers": "~1.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "~4.8", 30 | "loadsys/loadsys_codesniffer": "~3.0", 31 | "satooshi/php-coveralls": "~2.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Sitemap\\": "src" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Cake\\Test\\": "vendor/cakephp/cakephp/test", 41 | "Sitemap\\Test\\": "tests" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | '/sitemap'], 7 | function ($routes) { 8 | $routes->extensions(['xml']); 9 | $routes->connect('/', [ 10 | 'controller' => 'Sitemaps', 11 | 'plugin' => 'Sitemap', 12 | 'action' => 'index' 13 | ]); 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ./tests/TestCase 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ./vendor/ 36 | ./vendor/ 37 | 38 | ./tests/ 39 | ./tests/ 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Controller/AppController.php: -------------------------------------------------------------------------------- 1 | loadModel($table); 29 | $data[$table] = $tableInstance->find('forSitemap'); 30 | } 31 | 32 | $this->set('data', $data); 33 | $this->set('_serialize', false); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Lib/Iterators/ExtFilteredDirIterator.php: -------------------------------------------------------------------------------- 1 | allowed = $allowedExtensions; 41 | } 42 | } 43 | 44 | /** 45 | * accept 46 | * 47 | * Called for each item in the Iterator. Will allow the entry if: 48 | * - It is not ./ or ../ 49 | * - It is a sub-directory 50 | * - It is in the provided ::allowed array 51 | * - Or if no ::allowed array has been provided (which essentially allows all files and folders.) 52 | * 53 | * @access public 54 | * @return bool True for all non-dot directories, and files with extensions in $this->allowed. 55 | */ 56 | public function accept() { 57 | $current = parent::current(); 58 | 59 | return ( 60 | !$current->isDot() 61 | && 62 | ( 63 | !is_array($this->allowed) // No extensions defined. Allow everything through. 64 | || in_array($current->getExtension(), $this->allowed) // Allow if extension is in provided list. 65 | || $current->isDir() // Allow if entry is a sub-directory. 66 | ) 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Lib/Iterators/PagesIterator.php: -------------------------------------------------------------------------------- 1 | request->webroot from the calling Controller. Used 34 | * when generating individual record arrays. 35 | * 36 | * @var string 37 | */ 38 | private $webroot = null; 39 | 40 | /** 41 | * __construct 42 | * 43 | * Creates a new ExtFilteredDirIterator (which is based on a 44 | * DirectoryIterator) that spits out image record arrays instead 45 | * of SplFileInfo objects. Suitable for passing directly to a 46 | * view and used in a foreach() loop directly. 47 | * 48 | * @param string $path Filesystem path for the folder to read. 49 | * @param array $depth An ordered array of intermediate folders 50 | * between the image root folder and the 51 | * current directory represented by 52 | * basename($path). 53 | * @param string $webroot The relative Cake webroot as returned 54 | * in a Controller by $this->request->webroot. 55 | * (There is no static access to this property, 56 | * hence having to pass it in.) 57 | * @param array $allowedExtensions An optional array of file extensions 58 | * to filter the resulting directory 59 | * list against. 60 | */ 61 | public function __construct($path, $depth, $webroot, $allowedExtensions = null) { 62 | $this->depth = $depth; 63 | $this->webroot = $webroot; 64 | if (is_array($allowedExtensions)) { 65 | $this->allowed = $allowedExtensions; // Save this to pass into subfolder count calculations. 66 | } 67 | 68 | parent::__construct($path, $this->allowed); 69 | } 70 | 71 | /** 72 | * accept 73 | * 74 | * In addition to filtering by filename extension in our parent 75 | * ExtFilteredDirIterator, also filter out any .ctp files that begin with 76 | * 'admin_' to prevent modifying a management view. 77 | * 78 | * @return bool True if the basename of the current file does not begin with "admin_". 79 | */ 80 | public function accept() { 81 | $current = parent::current(); 82 | 83 | return parent::accept() && (strpos($current->getBasename('.ctp'), 'admin_') !== 0); 84 | } 85 | 86 | /** 87 | * current 88 | * 89 | * Takes a Fileinfo object representing a file or folder from the 90 | * $this->_standardPath folder and an array of elements representing the 91 | * intermediate folder names to the file from the "root" in order. 92 | * Generates a new "Page" record using this information and returns the 93 | * resulting array. Example returned record: 94 | * 95 | * array( 96 | * 'basename' => 'file.ext', 97 | * 'filename' => 'subfolder/file.ext', 98 | * 'title' => 'File', 99 | * 'url' => '/subfolder/file.ext', 100 | * 'bytes' => 14566, 101 | * 'modified' => 1383075606, // unix timestamp 102 | * ); 103 | * 104 | * Folders will have an dditional [children] element that contains an 105 | * integer count of the (matching) folder contents from that sub-directory. 106 | * 107 | * @return array A fake [Page] record including basename, filename 108 | * (including relative path from the image root), 109 | * display_url (as an absolute URL without the FQDN), url 110 | * (relative to Cake's root folder), bytes, modified 111 | * (and 'children', in the case of directories) fields. 112 | */ 113 | public function current() { 114 | $fileinfo = parent::current(); 115 | $depth = $this->depth; 116 | $parent = implode('/', $depth); 117 | $url = str_replace(WWW_ROOT, $this->webroot, Router::url(array_merge( 118 | [ 119 | 'plugin' => false, 120 | 'controller' => 'pages', 121 | 'action' => 'display', 122 | ], 123 | $depth, 124 | [$fileinfo->getBasename('.ctp')] 125 | ))); 126 | $page = [ 127 | 'basename' => $fileinfo->getFilename(), 128 | 'filename' => ltrim($parent . '/' . $fileinfo->getFilename(), '/'), 129 | 'title' => Inflector::humanize($fileinfo->getBasename('.ctp')), 130 | 'url' => str_replace($this->webroot, '/', $url), 131 | 'bytes' => $fileinfo->getSize(), 132 | 'modified' => $fileinfo->getMTime(), 133 | ]; 134 | if ($fileinfo->isDir()) { // Override the target URL and get a count of the children in subdirs. 135 | $page['url'] = array_merge( 136 | [ 137 | 'plugin' => false, 138 | 'controller' => 'pages', 139 | 'action' => 'index', 140 | ], 141 | $depth, 142 | [$page['basename']] 143 | ); 144 | $page['children'] = iterator_count(new ExtFilteredDirIterator($fileinfo->getRealPath(), $this->allowed)); 145 | } 146 | 147 | return $page; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Model/Behavior/SitemapBehavior.php: -------------------------------------------------------------------------------- 1 | 'default', 27 | 'lastmod' => 'modified', 28 | 'changefreq' => 'daily', 29 | 'priority' => '0.9', 30 | 'conditions' => [], 31 | 'order' => [], 32 | 'fields' => [], 33 | 'implementedMethods' => [ 34 | 'getUrl' => 'returnUrlForEntity', 35 | ], 36 | 'implementedFinders' => [ 37 | 'forSitemap' => 'findSitemapRecords', 38 | ], 39 | ]; 40 | 41 | /** 42 | * Constructor 43 | * 44 | * Merges config with the default and store in the config property 45 | * 46 | * @param \Cake\ORM\Table $table The table this behavior is attached to. 47 | * @param array $config The config for this behavior. 48 | */ 49 | public function __construct(Table $table, array $config = []) { 50 | parent::__construct($table, $config); 51 | } 52 | 53 | /** 54 | * Constructor hook method. 55 | * 56 | * Implement this method to avoid having to overwrite 57 | * the constructor and call parent. 58 | * 59 | * @param array $config The configuration settings provided to this behavior. 60 | * @return void 61 | */ 62 | public function initialize(array $config) { 63 | parent::initialize($config); 64 | } 65 | 66 | /** 67 | * Return the URL for the primary view action for an Entity. 68 | * 69 | * @param \Cake\ORM\Entity $entity Entity object passed in to return the url for. 70 | * @return string Returns the URL string. 71 | */ 72 | public function returnUrlForEntity(Entity $entity) { 73 | return Router::url( 74 | [ 75 | 'plugin' => null, 76 | 'prefix' => null, 77 | 'controller' => $this->_table->alias(), 78 | 'action' => 'view', 79 | $entity->{$this->_table->primaryKey()}, 80 | ], 81 | true 82 | ); 83 | } 84 | 85 | /** 86 | * Find the Sitemap Records for a Table. 87 | * 88 | * @param \Cake\ORM\Query $query The Query being modified. 89 | * @param array $options The array of options for the find. 90 | * @return \Cake\ORM\Query Returns the modified Query object. 91 | */ 92 | public function findSitemapRecords(Query $query, array $options) { 93 | $query = $query 94 | ->where($this->_config['conditions']) 95 | ->cache("sitemap_{$query->repository()->alias()}", $this->_config['cacheConfigKey']) 96 | ->order($this->_config['order']) 97 | ->formatResults(function ($results) { 98 | return $this->mapResults($results); 99 | }); 100 | 101 | if (!empty($this->_config['fields'])) { 102 | $query = $query->select($this->_config['fields']); 103 | } 104 | 105 | return $query; 106 | } 107 | 108 | /** 109 | * Format Results method to take the ResultSetInterface and map it to add 110 | * calculated fields for the Sitemap. 111 | * 112 | * @param \Cake\Datasource\ResultSetInterface $results The results of a Query 113 | * operation. 114 | * @return \Cake\Collection\CollectionInterface Returns the modified collection 115 | * of Results. 116 | */ 117 | public function mapResults(ResultSetInterface $results) { 118 | return $results->map(function ($entity) { 119 | return $this->mapEntity($entity); 120 | }); 121 | } 122 | 123 | /** 124 | * Modify an entity with new `_` fields for the Sitemap display. 125 | * 126 | * @param \Cake\ORM\Entity $entity The entity being modified. 127 | * @return \Cake\ORM\Entity Returns the modified entity. 128 | */ 129 | public function mapEntity(Entity $entity) { 130 | $entity['_loc'] = $this->_table->getUrl($entity); 131 | $entity['_lastmod'] = $entity->{$this->_config['lastmod']}; 132 | $entity['_changefreq'] = $this->_config['changefreq']; 133 | $entity['_priority'] = $this->_config['priority']; 134 | 135 | return $entity; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Template/Sitemaps/index.ctp: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | $dataForKey): ?> 6 |

7 | 8 | 9 | 10 | 13 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 36 | 39 | 40 | 41 | 42 |
11 | 12 | 14 | 15 | 17 | 18 | 20 | 21 |
28 | _loc) ?> 29 | 31 | _priority) ?> 32 | 34 | _changefreq) ?> 35 | 37 | _lastmod) ?> 38 |
43 | 44 | -------------------------------------------------------------------------------- /src/Template/Sitemaps/xml/index.ctp: -------------------------------------------------------------------------------- 1 | ' ?> 2 | 3 | $dataForKey): ?> 4 | 5 | 6 | 7 | 8 | _loc) ?> 9 | 10 | 11 | _lastmod) ?> 12 | 13 | 14 | _changefreq) ?> 15 | 16 | 17 | _priority) ?> 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/App/Template/Layout/default.ctp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Html->charset() ?> 5 | 6 | 7 | <?= $this->fetch('title') ?> 8 | 9 | Html->meta('icon') ?> 10 | 11 | fetch('meta') ?> 12 | fetch('css') ?> 13 | fetch('script') ?> 14 | 15 | 16 | Flash->render() ?> 17 |
18 | fetch('content') ?> 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/Fixture/PagesFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => 'UUID primary key.', 'precision' => null], 21 | 'title' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => 'Title of the record.', 'precision' => null, 'fixed' => null], 22 | 'keywords' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => 'Meta keywords of the record.', 'precision' => null, 'fixed' => null], 23 | 'description' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => 'Meta description of the record.', 'precision' => null, 'fixed' => null], 24 | 'body' => ['type' => 'text', 'length' => null, 'null' => false, 'default' => null, 'comment' => 'Main (HTML) body content for this page.', 'precision' => null], 25 | 'is_indexed' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => '1', 'comment' => 'Boolean field that determines if a Page is indexable by search engines.', 'precision' => null], 26 | 'created' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => 'Date and time of record creation.', 'precision' => null], 27 | 'modified' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => 'Date and time of last modification.', 'precision' => null], 28 | '_constraints' => [ 29 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], 30 | ], 31 | '_options' => [ 32 | 'engine' => 'InnoDB', 33 | 'collation' => 'utf8_general_ci' 34 | ], 35 | ]; 36 | // @codingStandardsIgnoreEnd 37 | 38 | /** 39 | * Records 40 | * 41 | * @var array 42 | */ 43 | public $records = [ 44 | [ 45 | 'id' => '3b65a356-6df8-11e5-b2cc-000c29a33c4c', 46 | 'title' => 'Test Page 1', 47 | 'keywords' => 'Lorem ipsum dolor sit amet', 48 | 'description' => 'Lorem ipsum dolor sit amet', 49 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 50 | 'is_indexed' => 1, 51 | 'created' => '2015-10-08 21:27:04', 52 | 'modified' => '2015-10-08 21:27:04', 53 | ], 54 | [ 55 | 'id' => '55057c21-eab2-40f1-bea9-b24f7bc4f74e', 56 | 'title' => 'Test Page 2', 57 | 'keywords' => 'Lorem ipsum dolor sit amet', 58 | 'description' => 'Lorem ipsum dolor sit amet', 59 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 60 | 'is_indexed' => 1, 61 | 'created' => '2015-10-08 21:27:04', 62 | 'modified' => '2015-10-08 21:27:04', 63 | ], 64 | [ 65 | 'id' => '05f79d0f-efd1-49b4-b627-c8721bdcc635', 66 | 'title' => 'Test Page 3', 67 | 'keywords' => 'Lorem ipsum dolor sit amet', 68 | 'description' => 'Lorem ipsum dolor sit amet', 69 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 70 | 'is_indexed' => 1, 71 | 'created' => '2015-10-08 21:27:04', 72 | 'modified' => '2015-10-08 21:27:04', 73 | ], 74 | [ 75 | 'id' => '05f79d0f-efd2-49b4-b627-c8721bdcc635', 76 | 'title' => 'Test Page 4', 77 | 'keywords' => 'Lorem ipsum dolor sit amet', 78 | 'description' => 'Lorem ipsum dolor sit amet', 79 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 80 | 'is_indexed' => 1, 81 | 'created' => '2015-10-08 21:27:04', 82 | 'modified' => '2015-10-08 21:27:04', 83 | ], 84 | [ 85 | 'id' => '25f79d0f-efd2-49b4-b627-c8721bdcc635', 86 | 'title' => 'Test Page 5', 87 | 'keywords' => 'Lorem ipsum dolor sit amet', 88 | 'description' => 'Lorem ipsum dolor sit amet', 89 | 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 90 | 'is_indexed' => 0, 91 | 'created' => '2015-10-08 21:27:04', 92 | 'modified' => '2015-10-08 21:27:04', 93 | ], 94 | ]; 95 | } 96 | -------------------------------------------------------------------------------- /tests/TestCase/Controller/SitemapsControllerTest.php: -------------------------------------------------------------------------------- 1 | Pages = TableRegistry::get('Pages'); 36 | $sitemapConfig = []; 37 | $this->Pages->addBehavior('Sitemap.Sitemap', $sitemapConfig); 38 | $this->setupSession(); 39 | } 40 | 41 | /** 42 | * tearDown method 43 | * 44 | * @return void 45 | */ 46 | public function tearDown() { 47 | unset($this->Pages); 48 | 49 | Configure::clear(); 50 | 51 | parent::tearDown(); 52 | } 53 | 54 | /** 55 | * setup Session properties needed for the controller level tests 56 | * 57 | * @return void 58 | */ 59 | public function setupSession() { 60 | $this->session([]); 61 | } 62 | 63 | /** 64 | * Test index method with no models defined 65 | * 66 | * @return void 67 | * @covers \Sitemap\Controller\SitemapsController::index 68 | */ 69 | public function testIndexNoModels() { 70 | $Controller = $this->getMock( 71 | '\Sitemap\Controller\SitemapsController', 72 | ['set'] 73 | ); 74 | 75 | $Controller->expects($this->at(0)) 76 | ->method('set') 77 | ->with('data', []) 78 | ->will($this->returnValue(true)); 79 | 80 | $Controller->expects($this->at(1)) 81 | ->method('set') 82 | ->with('_serialize', false) 83 | ->will($this->returnValue(true)); 84 | 85 | $Controller->index(); 86 | } 87 | 88 | /** 89 | * Test index method with no models defined 90 | * 91 | * @return void 92 | * @covers \Sitemap\Controller\SitemapsController::index 93 | */ 94 | public function testIndexWithModels() { 95 | Configure::write('Sitemap.tables', ['Pages']); 96 | $pagesFindQuery = $this->Pages->find('forSitemap'); 97 | 98 | $Controller = $this->getMock( 99 | '\Sitemap\Controller\SitemapsController', 100 | ['set', 'loadModel'] 101 | ); 102 | 103 | $Controller->expects($this->at(1)) 104 | ->method('set') 105 | ->with('data', ['Pages' => $pagesFindQuery]) 106 | ->will($this->returnValue(true)); 107 | 108 | $Controller->expects($this->at(2)) 109 | ->method('set') 110 | ->with('_serialize', false) 111 | ->will($this->returnValue(true)); 112 | 113 | $Controller->expects($this->once()) 114 | ->method('loadModel') 115 | ->with('Pages') 116 | ->willReturn($this->Pages); 117 | 118 | $Controller->Pages = $this->Pages; 119 | 120 | $Controller->index(); 121 | } 122 | 123 | /** 124 | * Test the index method that the correct route is loaded and works. 125 | * 126 | * @return void 127 | * @covers \Sitemap\Controller\SitemapsController::index 128 | */ 129 | public function testIndexAccess() { 130 | $this->get('/sitemap.xml'); 131 | 132 | $this->assertResponseOk(); 133 | } 134 | 135 | /** 136 | * Test that the index method can execute finds on namespaced plugin tables 137 | * 138 | * @return void 139 | */ 140 | public function testLoadingPluginTables() { 141 | $exampleTableName = 'Example/Plugin.Posts'; 142 | Configure::write('Sitemap.tables', [$exampleTableName]); 143 | 144 | $tableInstance = new Table([ 145 | 'registryAlias' => 'Example/Plugin.Posts', 146 | 'alias' => 'Posts', 147 | 'table' => 'posts', 148 | 'schema' => new TableSchema('posts', [ 149 | 'id' => ['type' => 'integer'], 150 | 'title' => ['type' => 'string'], 151 | ]), 152 | ]); 153 | $tableInstance->addBehavior('Sitemap.Sitemap'); 154 | 155 | $Controller = $this->getMockBuilder(SitemapsController::class) 156 | ->setMethods(['loadModel']) 157 | ->getMock(); 158 | 159 | $Controller->expects($this->once()) 160 | ->method('loadModel') 161 | ->with($exampleTableName) 162 | ->willReturn($tableInstance); 163 | 164 | $Controller->index(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/TestCase/Model/Behavior/SitemapBehaviorTest.php: -------------------------------------------------------------------------------- 1 | Pages = TableRegistry::get('Pages'); 54 | $sitemapConfig = []; 55 | $this->Pages->addBehavior('Sitemap.Sitemap', $sitemapConfig); 56 | } 57 | 58 | /** 59 | * tearDown method 60 | * 61 | * @return void 62 | */ 63 | public function tearDown() { 64 | unset($this->Pages); 65 | 66 | parent::tearDown(); 67 | } 68 | 69 | /** 70 | * Test initial setup, essentially verify configuartion is passing in correctly. 71 | * 72 | * @return void 73 | */ 74 | public function testInitialization() { 75 | $sitemapConfig = []; 76 | $configOut = [ 77 | 'cacheConfigKey' => 'default', 78 | 'lastmod' => 'modified', 79 | 'changefreq' => 'daily', 80 | 'priority' => '0.9', 81 | 'conditions' => [], 82 | 'order' => [], 83 | 'fields' => [], 84 | 'implementedMethods' => [ 85 | 'getUrl' => 'returnUrlForEntity', 86 | ], 87 | 'implementedFinders' => [ 88 | 'forSitemap' => 'findSitemapRecords', 89 | ], 90 | ]; 91 | 92 | $Sitemap = new TestSitemapBehavior($this->Pages, $sitemapConfig); 93 | $this->assertEquals( 94 | $configOut, 95 | $Sitemap->_config, 96 | 'On an empty configuration array, the default configuration should be equal' 97 | ); 98 | 99 | $sitemapConfig = [ 100 | 'cacheConfigKey' => 'canary', 101 | 'lastmod' => 'lastmoddate', 102 | 'fields' => ['id', 'modified'], 103 | ]; 104 | $configOut = [ 105 | 'cacheConfigKey' => 'canary', 106 | 'lastmod' => 'lastmoddate', 107 | 'changefreq' => 'daily', 108 | 'priority' => '0.9', 109 | 'conditions' => [], 110 | 'order' => [], 111 | 'fields' => ['id', 'modified'], 112 | 'implementedMethods' => [ 113 | 'getUrl' => 'returnUrlForEntity', 114 | ], 115 | 'implementedFinders' => [ 116 | 'forSitemap' => 'findSitemapRecords', 117 | ], 118 | ]; 119 | 120 | $Sitemap = new TestSitemapBehavior($this->Pages, $sitemapConfig); 121 | $this->assertEquals( 122 | $configOut, 123 | $Sitemap->_config, 124 | 'On an valid configuration array, the configuration should be updated to match.' 125 | ); 126 | } 127 | 128 | /** 129 | * Test returnUrlForEntity method 130 | * 131 | * @return void 132 | */ 133 | public function testReturnUrlForEntity() { 134 | $id = 'canary'; 135 | $entity = new Entity([ 136 | 'id' => $id, 137 | ]); 138 | $this->assertEquals( 139 | "/pages/view/{$id}", 140 | $this->Pages->getUrl($entity), 141 | 'The method getUrl should return a valid url string for the passed entity.' 142 | ); 143 | } 144 | 145 | /** 146 | * Test the url for a plugin entity is correct 147 | * 148 | * @return void 149 | */ 150 | public function testReturnUrlForPluginEntity() { 151 | Router::connect('/posts/view/:id', ['controller' => 'Posts', 'action' => 'view'], ['id' => '[\d]+', 'pass' => ['id']]); 152 | 153 | $exampleTableName = 'Example/Plugin.Posts'; 154 | Configure::write('Sitemap.tables', [$exampleTableName]); 155 | 156 | $tableInstance = new Table([ 157 | 'registryAlias' => 'Example/Plugin.Posts', 158 | 'alias' => 'Posts', 159 | 'table' => 'posts', 160 | 'schema' => new TableSchema('posts', [ 161 | 'id' => ['type' => 'integer'], 162 | 'title' => ['type' => 'string'], 163 | ]), 164 | ]); 165 | $tableInstance->setPrimaryKey('id'); 166 | $tableInstance->addBehavior('Sitemap.Sitemap'); 167 | 168 | $post = new Entity([ 169 | 'id' => 1, 170 | 'title' => 'First post', 171 | ], [ 172 | 'source' => $tableInstance, 173 | ]); 174 | 175 | $expected = '/posts/view/1'; 176 | 177 | $behavior = $tableInstance->behaviors()->get('Sitemap'); 178 | 179 | $this->assertEquals($expected, $behavior->returnUrlForEntity($post)); 180 | } 181 | 182 | /** 183 | * Test ::findSitemapRecords when the Fields config is set. 184 | * 185 | * @return void 186 | */ 187 | public function testFindSitemapRecordsFields() { 188 | $configs = [ 189 | 'conditions' => [ 190 | 'field1' => 'value1', 191 | ], 192 | 'cacheConfigKey' => 'cache-key-asdf', 193 | 'order' => [ 194 | 'field2' => 'value2', 195 | ], 196 | 'fields' => [ 197 | 'field3' => 'value3', 198 | ], 199 | ]; 200 | $options = [ 201 | 'foo' => 'baz', 202 | ]; 203 | 204 | $TableMock = $this 205 | ->getMockBuilder('\Cake\ORM\Table') 206 | ->disableOriginalConstructor() 207 | ->getMock(); 208 | $SitemapBehavior = $this 209 | ->getMockBuilder('\Sitemap\Model\Behavior\SitemapBehavior') 210 | ->setMethods(['mapResults']) 211 | ->setConstructorArgs([$TableMock, $configs]) 212 | ->getMock(); 213 | 214 | $QueryMock = $this->getMockBuilder('\Cake\ORM\Query') 215 | ->setMethods([ 216 | 'where', 217 | 'cache', 218 | 'order', 219 | 'formatResults', 220 | 'select', 221 | 'repository', 222 | 'alias', 223 | ]) 224 | ->disableOriginalConstructor() 225 | ->getMock(); 226 | 227 | $QueryMock->expects($this->once()) 228 | ->method('where') 229 | ->with($configs['conditions']) 230 | ->will($this->returnSelf()); 231 | $QueryMock->expects($this->once()) 232 | ->method('repository') 233 | ->with() 234 | ->will($this->returnSelf()); 235 | $QueryMock->expects($this->once()) 236 | ->method('alias') 237 | ->with() 238 | ->will($this->returnValue('alias-canary')); 239 | $QueryMock->expects($this->once()) 240 | ->method('cache') 241 | ->with('sitemap_alias-canary', $configs['cacheConfigKey']) 242 | ->will($this->returnSelf()); 243 | $QueryMock->expects($this->once()) 244 | ->method('order') 245 | ->with($configs['order']) 246 | ->will($this->returnSelf()); 247 | $QueryMock->expects($this->once()) 248 | ->method('formatResults') 249 | ->with($this->isInstanceOf('closure')) 250 | ->will($this->returnSelf()); 251 | $QueryMock->expects($this->once()) 252 | ->method('select') 253 | ->with($configs['fields']) 254 | ->will($this->returnValue('canary')); 255 | 256 | $output = $SitemapBehavior->findSitemapRecords($QueryMock, $options); 257 | $this->assertEquals( 258 | 'canary', 259 | $output, 260 | 'The output from ::findSitemapRecords should be our mocked response from the Query Object.' 261 | ); 262 | } 263 | 264 | /** 265 | * Test ::findSitemapRecords when the Fields config is not set. 266 | * 267 | * @return void 268 | */ 269 | public function testFindSitemapRecordsNoFields() { 270 | $configs = [ 271 | 'conditions' => [ 272 | 'field1' => 'value1', 273 | ], 274 | 'cacheConfigKey' => 'cache-key-asdf', 275 | 'order' => [ 276 | 'field2' => 'value2', 277 | ], 278 | ]; 279 | $options = [ 280 | 'foo' => 'baz', 281 | ]; 282 | 283 | $TableMock = $this 284 | ->getMockBuilder('\Cake\ORM\Table') 285 | ->disableOriginalConstructor() 286 | ->getMock(); 287 | $SitemapBehavior = $this 288 | ->getMockBuilder('\Sitemap\Model\Behavior\SitemapBehavior') 289 | ->setMethods(['mapResults']) 290 | ->setConstructorArgs([$TableMock, $configs]) 291 | ->getMock(); 292 | 293 | $QueryMock = $this->getMockBuilder('\Cake\ORM\Query') 294 | ->setMethods([ 295 | 'where', 296 | 'cache', 297 | 'order', 298 | 'formatResults', 299 | 'select', 300 | 'repository', 301 | 'alias', 302 | ]) 303 | ->disableOriginalConstructor() 304 | ->getMock(); 305 | 306 | $QueryMock->expects($this->once()) 307 | ->method('where') 308 | ->with($configs['conditions']) 309 | ->will($this->returnSelf()); 310 | $QueryMock->expects($this->once()) 311 | ->method('repository') 312 | ->with() 313 | ->will($this->returnSelf()); 314 | $QueryMock->expects($this->once()) 315 | ->method('alias') 316 | ->with() 317 | ->will($this->returnValue('alias-canary')); 318 | $QueryMock->expects($this->once()) 319 | ->method('cache') 320 | ->with('sitemap_alias-canary', $configs['cacheConfigKey']) 321 | ->will($this->returnSelf()); 322 | $QueryMock->expects($this->once()) 323 | ->method('order') 324 | ->with($configs['order']) 325 | ->will($this->returnSelf()); 326 | $QueryMock->expects($this->once()) 327 | ->method('formatResults') 328 | ->with($this->isInstanceOf('closure')) 329 | ->will($this->returnValue('canary')); 330 | $QueryMock->expects($this->never()) 331 | ->method('select'); 332 | 333 | $output = $SitemapBehavior->findSitemapRecords($QueryMock, $options); 334 | $this->assertEquals( 335 | 'canary', 336 | $output, 337 | 'The output from ::findSitemapRecords should be our mocked response from the Query Object.' 338 | ); 339 | } 340 | 341 | /** 342 | * Test the mapEntity method. 343 | * 344 | * @return void 345 | */ 346 | public function testMapEntity() { 347 | $pageId = '3b65a356-6df8-11e5-b2cc-000c29a33c4c'; 348 | $sitemapConfig = []; 349 | $Sitemap = new TestSitemapBehavior($this->Pages, $sitemapConfig); 350 | 351 | $entity = $this->Pages->get($pageId); 352 | $entity = $Sitemap->mapEntity($entity); 353 | $this->assertEquals( 354 | "/pages/view/{$pageId}", 355 | $entity->_loc, 356 | 'The _loc field should be set to our standard url' 357 | ); 358 | 359 | $this->assertEquals( 360 | new Time('2015-10-08 21:27:04'), 361 | $entity->_lastmod, 362 | 'The _loc field should be set to our standard url' 363 | ); 364 | 365 | $this->assertEquals( 366 | "daily", 367 | $entity->_changefreq, 368 | 'The _changefreq field should be set to our standard daily' 369 | ); 370 | 371 | $this->assertEquals( 372 | "0.9", 373 | $entity->_priority, 374 | 'The _priority field should be set to our standard 0.9' 375 | ); 376 | unset($Sitemap); 377 | 378 | // test with a modified Sitemap Configuration 379 | $sitemapConfig = [ 380 | 'priority' => '0.1', 381 | 'changefreq' => 'weekly', 382 | ]; 383 | $Sitemap = new TestSitemapBehavior($this->Pages, $sitemapConfig); 384 | 385 | $entity = $this->Pages->get($pageId); 386 | $entity = $Sitemap->mapEntity($entity); 387 | $this->assertEquals( 388 | "/pages/view/{$pageId}", 389 | $entity->_loc, 390 | 'The _loc field should be set to our standard url' 391 | ); 392 | 393 | $this->assertEquals( 394 | new Time('2015-10-08 21:27:04'), 395 | $entity->_lastmod, 396 | 'The _loc field should be set to our standard url' 397 | ); 398 | 399 | $this->assertEquals( 400 | "weekly", 401 | $entity->_changefreq, 402 | 'The _changefreq field should be set to our modified weekly' 403 | ); 404 | 405 | $this->assertEquals( 406 | "0.1", 407 | $entity->_priority, 408 | 'The _priority field should be set to our modified 0.1' 409 | ); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'App', 22 | 'encoding' => 'UTF-8', 23 | 'paths' => [ 24 | 'templates' => [APP . 'Template' . DS], 25 | ], 26 | ]); 27 | Cake\Core\Configure::write('debug', true); 28 | 29 | date_default_timezone_set('UTC'); 30 | mb_internal_encoding('UTF-8'); 31 | 32 | $Tmp = new Cake\Filesystem\Folder(TMP); 33 | $Tmp->create(TMP . 'cache/models', 0770); 34 | $Tmp->create(TMP . 'cache/persistent', 0770); 35 | $Tmp->create(TMP . 'cache/views', 0770); 36 | 37 | $cache = [ 38 | 'default' => [ 39 | 'engine' => 'File', 40 | 'path' => CACHE, 41 | ], 42 | '_cake_core_' => [ 43 | 'className' => 'File', 44 | 'prefix' => 'sitemap_', 45 | 'path' => CACHE . 'persistent/', 46 | 'serialize' => true, 47 | 'duration' => '+10 seconds', 48 | ], 49 | '_cake_model_' => [ 50 | 'className' => 'File', 51 | 'prefix' => 'sitemap_', 52 | 'path' => CACHE . 'models/', 53 | 'serialize' => 'File', 54 | 'duration' => '+10 seconds', 55 | ], 56 | ]; 57 | 58 | Cake\Cache\Cache::config($cache); 59 | 60 | Cake\Core\Plugin::load('Sitemap', ['path' => ROOT . DS, 'routes' => true]); 61 | 62 | // Ensure default test connection is defined 63 | if (!getenv('db_class')) { 64 | putenv('db_class=Cake\Database\Driver\Sqlite'); 65 | putenv('db_dsn=sqlite::memory:'); 66 | } 67 | 68 | Cake\Datasource\ConnectionManager::config('test', [ 69 | 'className' => 'Cake\Database\Connection', 70 | 'driver' => getenv('db_class'), 71 | 'dsn' => getenv('db_dsn'), 72 | 'database' => getenv('db_database'), 73 | 'username' => getenv('db_username'), 74 | 'password' => getenv('db_password'), 75 | 'timezone' => 'UTC', 76 | 'quoteIdentifiers' => true, 77 | 'cacheMetadata' => true, 78 | ]); 79 | 80 | Cake\Routing\DispatcherFactory::add('Routing'); 81 | 82 | class_alias('Cake\Controller\Controller', 'App\Controller\AppController'); 83 | -------------------------------------------------------------------------------- /tests/config/routes.php: -------------------------------------------------------------------------------- 1 | connect('/pages/view/:id', [ 8 | 'controller' => 'Pages', 9 | 'action' => 'view', 10 | ], ['pass' => ['id']]); 11 | }); 12 | 13 | require ROOT . DS . 'config/routes.php'; 14 | -------------------------------------------------------------------------------- /webroot/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loadsys/CakePHP-Sitemap/0b0a4ad359898757eef745f92ad9456b6b2581b5/webroot/empty --------------------------------------------------------------------------------