├── Api ├── Data │ ├── PageNotFoundInterface.php │ └── PageNotFoundSearchResultsInterface.php └── PageNotFoundRepositoryInterface.php ├── Block ├── Adminhtml │ └── Page │ │ └── Not │ │ └── Found │ │ └── Edit │ │ ├── BackButton.php │ │ ├── DeleteButton.php │ │ ├── GenericButton.php │ │ ├── SaveAndContinueButton.php │ │ └── SaveButton.php └── Response │ └── Gone.php ├── CHANGELOG.md ├── Console └── Command │ ├── Clean.php │ └── Import.php ├── Controller ├── Adminhtml │ ├── Pagenotfound.php │ └── Pagenotfound │ │ ├── Delete.php │ │ ├── Edit.php │ │ ├── Index.php │ │ ├── InlineEdit.php │ │ ├── NewAction.php │ │ └── Save.php └── Response │ └── Gone.php ├── Cron └── Clean.php ├── Helper ├── Settings.php └── UrlCleanUp.php ├── MANUAL.md ├── Model ├── PageNotFound.php ├── PageNotFoundRepository.php ├── Pagenotfound │ └── DataProvider.php └── ResourceModel │ ├── PageNotFound.php │ └── PageNotFound │ └── Collection.php ├── Observer └── Controller │ └── ActionPredispatch.php ├── README.md ├── Ui └── Component │ └── Listing │ └── Column │ ├── Link.php │ └── PagenotfoundActions.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── di.xml │ ├── menu.xml │ ├── routes.xml │ └── system.xml ├── config.xml ├── crontab.xml ├── db_schema.xml ├── db_schema_whitelist.json ├── di.xml ├── frontend │ ├── events.xml │ └── routes.xml └── module.xml ├── registration.php └── view ├── adminhtml ├── layout │ ├── experius_pagenotfound_pagenotfound_edit.xml │ ├── experius_pagenotfound_pagenotfound_index.xml │ └── experius_pagenotfound_pagenotfound_new.xml └── ui_component │ ├── experius_page_not_found_form.xml │ └── experius_page_not_found_index.xml └── frontend ├── layout └── experius_pagenotfound_response_gone.xml └── templates └── response └── gone.phtml /Api/Data/PageNotFoundInterface.php: -------------------------------------------------------------------------------- 1 | __('Back'), 18 | 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), 19 | 'class' => 'back', 20 | 'sort_order' => 10 21 | ]; 22 | } 23 | 24 | /** 25 | * Get URL for back (reset) button 26 | * 27 | * @return string 28 | */ 29 | public function getBackUrl() 30 | { 31 | return $this->getUrl('*/*/'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Block/Adminhtml/Page/Not/Found/Edit/DeleteButton.php: -------------------------------------------------------------------------------- 1 | getModelId()) { 18 | $data = [ 19 | 'label' => __('Delete Redirect'), 20 | 'class' => 'delete', 21 | 'on_click' => 'deleteConfirm(\'' . __( 22 | 'Are you sure you want to do this?' 23 | ) . '\', \'' . $this->getDeleteUrl() . '\')', 24 | 'sort_order' => 20, 25 | ]; 26 | } 27 | return $data; 28 | } 29 | 30 | /** 31 | * Get URL for delete button 32 | * 33 | * @return string 34 | */ 35 | public function getDeleteUrl() 36 | { 37 | return $this->getUrl('*/*/delete', ['page_not_found_id' => $this->getModelId()]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Block/Adminhtml/Page/Not/Found/Edit/GenericButton.php: -------------------------------------------------------------------------------- 1 | context = $context; 19 | } 20 | 21 | /** 22 | * Return model ID 23 | * 24 | * @return int|null 25 | */ 26 | public function getModelId() 27 | { 28 | return $this->context->getRequest()->getParam('page_not_found_id'); 29 | } 30 | 31 | /** 32 | * Generate url by route and parameters 33 | * 34 | * @param string $route 35 | * @param array $params 36 | * @return string 37 | */ 38 | public function getUrl($route = '', $params = []) 39 | { 40 | return $this->context->getUrlBuilder()->getUrl($route, $params); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Block/Adminhtml/Page/Not/Found/Edit/SaveAndContinueButton.php: -------------------------------------------------------------------------------- 1 | __('Save and Continue Edit'), 18 | 'class' => 'save', 19 | 'data_attribute' => [ 20 | 'mage-init' => [ 21 | 'button' => ['event' => 'saveAndContinueEdit'], 22 | ], 23 | ], 24 | 'sort_order' => 80, 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Block/Adminhtml/Page/Not/Found/Edit/SaveButton.php: -------------------------------------------------------------------------------- 1 | __('Save Redirect'), 18 | 'class' => 'save primary', 19 | 'data_attribute' => [ 20 | 'mage-init' => ['button' => ['event' => 'save']], 21 | 'form-role' => 'save', 22 | ], 23 | 'sort_order' => 90, 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Block/Response/Gone.php: -------------------------------------------------------------------------------- 1 | pageConfig->addBodyClass('410'); 12 | $this->pageConfig->getTitle()->set('410 Gone'); 13 | //$this->pageConfig->setKeywords(); 14 | //$this->pageConfig->setDescription(); 15 | 16 | $pageMainTitle = $this->getLayout()->getBlock('page.main.title'); 17 | $pageMainTitle->setPageTitle(__('Whoops, our bad...')); 18 | 19 | return parent::_prepareLayout(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.3 (2024-05-31) 2 | 3 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.4.3) 4 | 5 | * [FEATURE][JIRA-123]added graphQL suport in base function to extend pagenotfoundgraphql *(simon vianen)* 6 | * [FEATURE][JIRA-123]git added docblock, fixt typo, changed from url creation for graphql to the top of the savePageNotFound Function *(simon vianen)* 7 | * [FEATURE][JIRA-123]Added my name to the composer.json *(simon vianen)* 8 | 9 | 10 | ## 1.4.2 (2024-03-26) 11 | 12 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.4.2) 13 | 14 | * [FEATURE][JIRA-123]removed unnecessary 404 counter when url is being redirected *(simon vianen)* 15 | 16 | 17 | ## 1.4.1 (2024-03-22) 18 | 19 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.4.1) 20 | 21 | * [FEATURE][IN23-62] updated CHANGELOG.md *(simon vianen)* 22 | * [FEATURE][IN23-325] updated CHANGELOG.md and README.md *(simon vianen)* 23 | * [FEATURE][IN23-284] added storeview to admin grid with filter *(simon vianen)* 24 | * [FEATURE][IN23-284] added '0' return to getStoreId if id is 'NUL' *(simon vianen)* 25 | * [FEATURE][IN23-284] Fixed typo's *(simon vianen)* 26 | 27 | 28 | ## 1.4.0 (2024-03-20) 29 | 30 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.4.0) 31 | 32 | * [feature][IN23-325]added cron job and consol command *(simon vianen)* 33 | * [FEATURE][IN23-325]Removed unused code and spel check *(simon vianen)* 34 | * [feature][IN23-325]restored refactord code *(simon vianen)* 35 | * [feature][IN23-325]added cron_config to config.xml *(simon vianen)* 36 | * [feature][IN23-325] chaged code to fit magento core *(simon vianen)* 37 | * [FEATURE][IN23-325]Shortend query buiding *(simon vianen)* 38 | 39 | 40 | ## 1.3.8 (2024-03-18) 41 | 42 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.8) 43 | 44 | * [FEATURE][IN23-62] added collumn last visited *(simon vianen)* 45 | 46 | 47 | ## 1.3.7 (2024-03-07) 48 | 49 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.7) 50 | 51 | * [FEATURE][JIRA-1024] changed the from_url to varchar 520 + added index for column *(Thomas Mondeel)* 52 | 53 | 54 | ## 1.3.6 (2024-02-02) 55 | 56 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.6) 57 | 58 | * [FEATURE][JIRA-123] compatible with magento-coding-standards 33 *(Matthijs Iriks)* 59 | 60 | 61 | ## 1.3.5 (2023-10-26) 62 | 63 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.5) 64 | 65 | * [BUGFIX][IN23-251] Convert setup scripts to db_schema.xml conform newer Magento 2 standards. *(Boris van Katwijk)* 66 | 67 | 68 | ## 1.3.4 (2023-05-09) 69 | 70 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.4) 71 | 72 | * Declared dynamic property to fix PHP8.2 error *(Experius)* 73 | 74 | 75 | ## 1.3.3 (2022-04-28) 76 | 77 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.3) 78 | 79 | * [BUGFIX] - Deprecated Functionality: explode(): Passing null to parameter #2 ($string) of type string is deprecated *(Ruben Panis)* 80 | 81 | 82 | ## 1.3.2 (2021-08-02) 83 | 84 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.2) 85 | 86 | * [BUGFIX] Use page_not_found_view ACL resource *(Lewis Voncken)* 87 | 88 | 89 | ## 1.3.1 (2021-07-15) 90 | 91 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.1) 92 | 93 | * Update README.md *(Experius)* 94 | * [BUGFIX][STJ-937] Added classWhitelist on the configurable object. *(Gijs Blanken)* 95 | 96 | 97 | ## 1.3.0 (2020-09-18) 98 | 99 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.3.0) 100 | 101 | * [PERFORMANCE] Added index to the experius_page_not_found *(Lewis Voncken)* 102 | 103 | 104 | ## 1.2.8 (2020-05-06) 105 | 106 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.8) 107 | 108 | * Added handout for product import to readme.md *(pascalexperius)* 109 | 110 | 111 | ## 1.2.7 (2019-11-27) 112 | 113 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.7) 114 | 115 | * [BUGFIX] Magento 2.3.3 admin grid fix *(dheesbeen)* 116 | 117 | 118 | ## 1.2.6 (2019-08-30) 119 | 120 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.6) 121 | 122 | * [BUGFIX] - Removed use of non-existent variable *(Ruben Panis)* 123 | * [REFACTOR] - Fixed some code standard bugs/warnings *(Ruben Panis)* 124 | * [BUGFIX] - Fixed exception message with correct variablename *(Ruben Panis)* 125 | * [REFACTOR] Change way to ignore exit with code sniffer, now passes checks *(Ruben Panis)* 126 | 127 | 128 | ## 1.2.5 (2018-01-11) 129 | 130 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.5) 131 | 132 | * [BUGFIX] Forward to 410 instate of redirecting to the controller to prevent a 302 redirect *(Lewis Voncken)* 133 | 134 | 135 | ## 1.2.4 (2017-10-24) 136 | 137 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.4) 138 | 139 | * [BUGFIX] Updated module.xml version to 1.0.1 *(Mr. Lewis)* 140 | 141 | 142 | ## 1.2.3 (2017-10-20) 143 | 144 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.3) 145 | 146 | * [BUGFIX] import getoptions error, grid history save *(Derrick Heesbeen)* 147 | 148 | 149 | ## 1.2.2 (2017-10-20) 150 | 151 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.2) 152 | 153 | * [TASK] Repository changes Pull request Bart *(Derrick Heesbeen)* 154 | 155 | 156 | ## 1.2.1 (2017-10-20) 157 | 158 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.1) 159 | 160 | * [TASK] add duplicate from_url check *(Derrick Heesbeen)* 161 | 162 | 163 | ## 1.2.0 (2017-10-20) 164 | 165 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.2.0) 166 | 167 | * [TASK] added 410 redirect option and 410 response page *(Derrick Heesbeen)* 168 | 169 | 170 | ## 1.0.14 (2017-10-03) 171 | 172 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.14) 173 | 174 | * Revert "[TASK] Made from_url unique, if database already contains duplicates those are merged" *(Derrick Heesbeen)* 175 | 176 | 177 | ## 1.1.0 (2017-09-15) 178 | 179 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.1.0) 180 | 181 | * [TASK] Add compatibility with Experius ContentPage module *(bartlubbersen)* 182 | * [TASK] Made from_url unique, if database already contains duplicates those are merged [TASK] If new redirect is added through back-end but from_url already exists the page with that url is updated and warning is shown [BUGFIX] There can't be duplicate from_url's because those caused the issue that only the first could be redirected *(Bart Lubbersen)* 183 | 184 | 185 | ## 1.0.13 (2017-09-06) 186 | 187 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.13) 188 | 189 | * [FEATURE] find and replace command *(Derrick Heesbeen)* 190 | * [BUGFIX] moved replace script to separated module *(Derrick Heesbeen)* 191 | * [BUGFIX] Fix to make acl work *(Derrick Heesbeen)* 192 | 193 | 194 | ## 1.0.12 (2017-08-22) 195 | 196 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.12) 197 | 198 | * [BUGFIX] bug in import when using a replace url, bug with nog redirecting in live modus *(Derrick Heesbeen)* 199 | 200 | 201 | ## 1.0.11 (2017-08-22) 202 | 203 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.11) 204 | 205 | * [BUGFIX] unique command name in di xml decleration *(Derrick Heesbeen)* 206 | 207 | 208 | ## 1.0.10 (2017-08-22) 209 | 210 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.10) 211 | 212 | * [FEATURE] Console import script for csv redirect list *(Derrick Heesbeen)* 213 | 214 | 215 | ## 1.0.9 (2017-08-18) 216 | 217 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.9) 218 | 219 | * [BUGFIX] add params to redirect url check on excisting params *(Derrick Heesbeen)* 220 | 221 | 222 | ## 1.0.8 (2017-08-18) 223 | 224 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.8) 225 | 226 | * Update ActionPredispatch.php *(Derrick Heesbeen)* 227 | * [FEATURE] Made setting less complex :-) *(Derrick Heesbeen)* 228 | 229 | 230 | ## 1.0.6 (2017-08-18) 231 | 232 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.6) 233 | 234 | * [FEATURE] include params for saved from url *(Derrick Heesbeen)* 235 | 236 | 237 | ## 1.0.5 (2017-08-15) 238 | 239 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.5) 240 | 241 | * [FEATURE] added settings, added exluded params options *(Derrick Heesbeen)* 242 | 243 | 244 | ## 1.0.4 (2017-07-31) 245 | 246 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.4) 247 | 248 | * [BUGFIX] Temp Fix for redirect to work with fpc *(Derrick Heesbeen)* 249 | 250 | 251 | ## 1.0.3 (2017-07-27) 252 | 253 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.3) 254 | 255 | * [BUGFIX] solved fatal error of Interface implementation *(Lewis Voncken)* 256 | 257 | 258 | ## 1.0.2 (2017-07-25) 259 | 260 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.2) 261 | 262 | * Fix typos *(Tim Neutkens)* 263 | * [TASK] make link in grids and add params to redirect *(Derrick Heesbeen)* 264 | 265 | 266 | ## 1.0.1 (2017-06-22) 267 | 268 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.1) 269 | 270 | * [FEATURE] Updated readme with menu item location *(Derrick Heesbeen)* 271 | * [BUGFIX] admin form save issue, renamed to correct title *(Derrick Heesbeen)* 272 | 273 | 274 | ## 1.0.0 (2017-06-22) 275 | 276 | [View Release](git@github.com:experius/Magento-2-Module-PageNotFound.git/commits/tag/1.0.0) 277 | 278 | * first commit *(Derrick Heesbeen)* 279 | 280 | 281 | -------------------------------------------------------------------------------- /Console/Command/Clean.php: -------------------------------------------------------------------------------- 1 | setName("experius_pagenotfound:clean") 43 | ->setDescription("Cleanup old reports.") 44 | ->setDefinition($options); 45 | 46 | parent::configure(); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | protected function execute(InputInterface $input, OutputInterface $output) 53 | { 54 | if ($days = $input->getOption(self::DAYS)) { 55 | if(!is_numeric($days)) { 56 | $output->writeln($days . " is not a number"); 57 | return Cli::RETURN_FAILURE; 58 | } 59 | $output->writeln("Deleting everything older than " . $days . " days."); 60 | $deletionCount = $this->cleanUp->execute($days); 61 | } else { 62 | $output->writeln("No days given, using days from admin if available."); 63 | $deletionCount = $this->cleanUp->execute(); 64 | } 65 | $output->writeln('Removed ' . $deletionCount . ' from 404 reports.'); 66 | return Cli::RETURN_SUCCESS; 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Console/Command/Import.php: -------------------------------------------------------------------------------- 1 | csv = $csv; 47 | $this->state = $state; 48 | $this->directoryList = $directoryList; 49 | $this->pageNotFoundFactory = $pageNotFoundFactory; 50 | $this->storeRepository = $storeRepository; 51 | $this->storeManager = $storeManager; 52 | 53 | return parent::__construct(); 54 | } 55 | 56 | protected function execute( 57 | InputInterface $input, 58 | OutputInterface $output 59 | ) { 60 | 61 | $this->input = $input; 62 | $this->output = $output; 63 | 64 | $this->state->setAreaCode('adminhtml'); 65 | 66 | $csvRows = $this->readCsv(); 67 | 68 | $convertedRows = $this->convertRows($csvRows); 69 | 70 | if(!$this->input->getOption(self::DRY_RUN_OPTION)) { 71 | $this->importRedirects($convertedRows); 72 | } 73 | 74 | $this->renderConversion($convertedRows); 75 | 76 | } 77 | 78 | protected function convertRows($csvRows){ 79 | $rows = []; 80 | $count = 1; 81 | foreach($csvRows as $csvRow){ 82 | 83 | if(!$csvRow[1]){ 84 | continue; 85 | } 86 | 87 | $fromUrl = $this->replaceFromUrlDomain($csvRow[0]); 88 | $toUrl = $this->replaceToUrlDomain($csvRow[1]); 89 | 90 | $rows[] = ['from_url'=>$fromUrl,'to_url'=>$toUrl]; 91 | 92 | if($this->input->getOption(self::DRY_RUN_OPTION) && $count>=10){ 93 | break; 94 | } 95 | 96 | $count++; 97 | } 98 | return $rows; 99 | } 100 | 101 | protected function replaceToUrlDomain($url){ 102 | if($this->input->getOption(self::TO_URL_REPLACE_OPTION)){ 103 | $domain = $this->input->getOption(self::TO_URL_REPLACE_OPTION); 104 | $url = $this->replaceDomain($url,$domain); 105 | } 106 | return $url; 107 | } 108 | 109 | protected function replaceFromUrlDomain($url){ 110 | if($this->input->getOption(self::FROM_URL_REPLACE_OPTION)){ 111 | $domain = $this->input->getOption(self::FROM_URL_REPLACE_OPTION); 112 | $url = $this->replaceDomain($url,$domain); 113 | } 114 | return $url; 115 | } 116 | 117 | protected function replaceDomain($url,$domain){ 118 | $urlParts = parse_url($url); 119 | 120 | $params = (isset($urlParts['query']) && $urlParts['query']) ? '?' . $urlParts['query'] : ''; 121 | $path = (isset($urlParts['path']) && $urlParts['path']) ? $urlParts['path'] : ''; 122 | $url = $domain . $path . $params; 123 | 124 | return $url; 125 | } 126 | 127 | protected function importRedirects($rows){ 128 | foreach($rows as $row){ 129 | $this->savePageNotFound($row['from_url'],$row['to_url']); 130 | } 131 | } 132 | 133 | protected function savePageNotFound($fromUrl,$toUrl){ 134 | /* @var $pageNotFound \Experius\PageNotFound\Model\PageNotFound */ 135 | $pageNotFound = $this->pageNotFoundFactory->create(); 136 | 137 | $pageNotFound->load($fromUrl,'from_url'); 138 | $storeId = $this->getStoreView($fromUrl); 139 | 140 | $pageNotFound->setFromUrl($fromUrl); 141 | $pageNotFound->setToUrl($toUrl); 142 | $pageNotFound->setStoreId($storeId); 143 | $pageNotFound->save(); 144 | } 145 | 146 | protected function readCsv(){ 147 | 148 | $filePath = ($this->input->getOption(self::FILENAME_OPTION)) ? $this->input->getOption(self::FILENAME_OPTION) : $this->directoryList->getPath($this->fileDirectory) . "/" . self::FILENAME_IMPORT; 149 | 150 | if(!file_exists($filePath)){ 151 | $this->output->writeln("File not found"); 152 | return false; 153 | } 154 | 155 | $rows = $this->csv->setDelimiter(';')->setEnclosure('"')->getData($filePath); 156 | 157 | return $rows; 158 | } 159 | 160 | /** 161 | * {@inheritdoc} 162 | */ 163 | protected function configure() 164 | { 165 | $this->setName("experius_pagenotfound:import"); 166 | 167 | $this->setDescription("Import 404 redirects"); 168 | 169 | $this->addOption( 170 | self::FROM_URL_REPLACE_OPTION,'fr', 171 | InputOption::VALUE_OPTIONAL, 172 | 'Replace to url domain with' 173 | ); 174 | 175 | $this->addOption( 176 | self::TO_URL_REPLACE_OPTION,'to', 177 | InputOption::VALUE_OPTIONAL, 178 | 'Replace to url domain with' 179 | ); 180 | 181 | $this->addOption( 182 | self::FILENAME_OPTION,'f', 183 | InputOption::VALUE_OPTIONAL, 184 | 'Path to file' 185 | ); 186 | 187 | $this->addOption( 188 | self::DRY_RUN_OPTION,'d', 189 | InputOption::VALUE_OPTIONAL, 190 | 'Dry run option' 191 | ); 192 | 193 | parent::configure(); 194 | } 195 | 196 | private function renderConversion($data) 197 | { 198 | $table = new Table($this->output); 199 | 200 | $table->setHeaders(array('From url', 'To url')); 201 | $table->setRows($data); 202 | $table->render($data); 203 | } 204 | 205 | /** 206 | * @param $fromUrl 207 | * @param $storeId 208 | */ 209 | private function getStoreView($fromUrl) 210 | { 211 | $storeId = $this->storeManager->getDefaultStoreView()->getId(); 212 | foreach ($this->storeRepository->getList() as $store) { 213 | if (strpos($fromUrl, $store->getBaseUrl(), 0) !== false) { 214 | $storeId = $store->getId(); 215 | break; 216 | } 217 | } 218 | return $storeId; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound.php: -------------------------------------------------------------------------------- 1 | _coreRegistry = $coreRegistry; 21 | parent::__construct($context); 22 | } 23 | 24 | /** 25 | * Init page 26 | * 27 | * @param \Magento\Backend\Model\View\Result\Page $resultPage 28 | */ 29 | public function initPage($resultPage) 30 | { 31 | $resultPage->setActiveMenu(self::ADMIN_RESOURCE) 32 | ->addBreadcrumb(__('Experius'), __('Experius')) 33 | ->addBreadcrumb(__('Page Not Found'), __('Page Not Found')); 34 | return $resultPage; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/Delete.php: -------------------------------------------------------------------------------- 1 | resultRedirectFactory->create(); 18 | // check if we know what should be deleted 19 | $id = $this->getRequest()->getParam('page_not_found_id'); 20 | if ($id) { 21 | try { 22 | // init model and delete 23 | $model = $this->_objectManager->create('Experius\PageNotFound\Model\PageNotFound'); 24 | $model->load($id); 25 | $model->delete(); 26 | // display success message 27 | $this->messageManager->addSuccessMessage(__('You deleted the Page Not Found.')); 28 | // go to grid 29 | return $resultRedirect->setPath('*/*/'); 30 | } catch (\Exception $e) { 31 | // display error message 32 | $this->messageManager->addErrorMessage($e->getMessage()); 33 | // go back to edit form 34 | return $resultRedirect->setPath('*/*/edit', ['page_not_found_id' => $id]); 35 | } 36 | } 37 | // display error message 38 | $this->messageManager->addErrorMessage(__('We can\'t find a Page Not Found to delete.')); 39 | // go to grid 40 | return $resultRedirect->setPath('*/*/'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/Edit.php: -------------------------------------------------------------------------------- 1 | resultPageFactory = $resultPageFactory; 22 | parent::__construct($context, $coreRegistry); 23 | } 24 | 25 | /** 26 | * Edit action 27 | * 28 | * @return \Magento\Framework\Controller\ResultInterface 29 | */ 30 | public function execute() 31 | { 32 | // 1. Get ID and create model 33 | $id = $this->getRequest()->getParam('page_not_found_id'); 34 | $model = $this->_objectManager->create('Experius\PageNotFound\Model\PageNotFound'); 35 | 36 | // 2. Initial checking 37 | if ($id) { 38 | $model->load($id); 39 | if (!$model->getId()) { 40 | $this->messageManager->addErrorMessage(__('This Page Not Found no longer exists.')); 41 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 42 | $resultRedirect = $this->resultRedirectFactory->create(); 43 | return $resultRedirect->setPath('*/*/'); 44 | } 45 | } 46 | $this->_coreRegistry->register('experius_pagenotfound_page_not_found', $model); 47 | 48 | // 5. Build edit form 49 | /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ 50 | $resultPage = $this->resultPageFactory->create(); 51 | $this->initPage($resultPage)->addBreadcrumb( 52 | $id ? __('Edit Page Not Found') : __('New Page Not Found'), 53 | $id ? __('Edit Page Not Found') : __('New Page Not Found') 54 | ); 55 | $resultPage->getConfig()->getTitle()->prepend(__('Page Not Founds')); 56 | $resultPage->getConfig()->getTitle()->prepend($model->getId() ? $model->getTitle() : __('New Page Not Found')); 57 | return $resultPage; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/Index.php: -------------------------------------------------------------------------------- 1 | resultPageFactory = $resultPageFactory; 22 | parent::__construct($context); 23 | } 24 | 25 | /** 26 | * Index action 27 | * 28 | * @return \Magento\Framework\Controller\ResultInterface 29 | */ 30 | public function execute() 31 | { 32 | $resultPage = $this->resultPageFactory->create(); 33 | $resultPage->getConfig()->getTitle()->prepend(__("404 Reports")); 34 | return $resultPage; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/InlineEdit.php: -------------------------------------------------------------------------------- 1 | jsonFactory = $jsonFactory; 21 | } 22 | 23 | /** 24 | * Inline edit action 25 | * 26 | * @return \Magento\Framework\Controller\ResultInterface 27 | */ 28 | public function execute() 29 | { 30 | /** @var \Magento\Framework\Controller\Result\Json $resultJson */ 31 | $resultJson = $this->jsonFactory->create(); 32 | $error = false; 33 | $messages = []; 34 | 35 | if ($this->getRequest()->getParam('isAjax')) { 36 | $postItems = $this->getRequest()->getParam('items', []); 37 | if (!count($postItems)) { 38 | $messages[] = __('Please correct the data sent.'); 39 | $error = true; 40 | } else { 41 | foreach (array_keys($postItems) as $modelid) { 42 | /** @var \Experius\PageNotFound\Model\PageNotFound $model */ 43 | $model = $this->_objectManager->create('Experius\PageNotFound\Model\PageNotFound')->load($modelid); 44 | try { 45 | $model->setData(array_merge($model->getData(), $postItems[$modelid])); 46 | $model->save(); 47 | } catch (\Exception $e) { 48 | $messages[] = "[Page Not Found ID: {$modelid}] {$e->getMessage()}"; 49 | $error = true; 50 | } 51 | } 52 | } 53 | } 54 | 55 | return $resultJson->setData([ 56 | 'messages' => $messages, 57 | 'error' => $error 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/NewAction.php: -------------------------------------------------------------------------------- 1 | resultForwardFactory = $resultForwardFactory; 22 | parent::__construct($context, $coreRegistry); 23 | } 24 | 25 | /** 26 | * New action 27 | * 28 | * @return \Magento\Framework\Controller\ResultInterface 29 | */ 30 | public function execute() 31 | { 32 | /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ 33 | $resultForward = $this->resultForwardFactory->create(); 34 | return $resultForward->forward('edit'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Pagenotfound/Save.php: -------------------------------------------------------------------------------- 1 | dataPersistor = $dataPersistor; 23 | parent::__construct($context); 24 | } 25 | 26 | /** 27 | * Save action 28 | * 29 | * @return \Magento\Framework\Controller\ResultInterface 30 | */ 31 | public function execute() 32 | { 33 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 34 | $resultRedirect = $this->resultRedirectFactory->create(); 35 | $data = $this->getRequest()->getPostValue(); 36 | if ($data) { 37 | $id = $this->getRequest()->getParam('page_not_found_id'); 38 | 39 | $model = $this->_objectManager->create('Experius\PageNotFound\Model\PageNotFound')->load($id); 40 | if (!$model->getId() && $id) { 41 | $this->messageManager->addErrorMessage(__('This Page Not Found no longer exists.')); 42 | return $resultRedirect->setPath('*/*/'); 43 | } 44 | 45 | $model->setData($data); 46 | 47 | try { 48 | $model->save(); 49 | $this->messageManager->addSuccessMessage(__('You saved the Page Not Found.')); 50 | $this->dataPersistor->clear('experius_pagenotfound_page_not_found'); 51 | 52 | if ($this->getRequest()->getParam('back')) { 53 | return $resultRedirect->setPath('*/*/edit', ['page_not_found_id' => $model->getId()]); 54 | } 55 | return $resultRedirect->setPath('*/*/'); 56 | } catch (LocalizedException $e) { 57 | $this->messageManager->addErrorMessage($e->getMessage()); 58 | } catch (AlreadyExistsException $e){ 59 | $this->messageManager->addErrorMessage($e->getMessage()); 60 | } catch (\Exception $e) { 61 | $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Page Not Found.')); 62 | } 63 | 64 | $this->dataPersistor->set('experius_pagenotfound_page_not_found', $data); 65 | return $resultRedirect->setPath('*/*/edit', ['page_not_found_id' => $this->getRequest()->getParam('page_not_found_id')]); 66 | } 67 | return $resultRedirect->setPath('*/*/'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Controller/Response/Gone.php: -------------------------------------------------------------------------------- 1 | resultPageFactory = $resultPageFactory; 22 | parent::__construct($context); 23 | } 24 | 25 | /** 26 | * Execute view action 27 | * 28 | * @return \Magento\Framework\Controller\ResultInterface 29 | */ 30 | public function execute() 31 | { 32 | 33 | $resultPage = $this->resultPageFactory->create(); 34 | 35 | $resultPage->setStatusHeader(410, '1.1', 'Gone'); 36 | $resultPage->setHeader('Status', '410 Gone'); 37 | 38 | return $resultPage; 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /Cron/Clean.php: -------------------------------------------------------------------------------- 1 | settings->getIsCronEnabled()){ 34 | $this->logger->info(__("Cron is disabled for '404 reports'")); 35 | return; 36 | } 37 | $this->cleanHelper->execute(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Helper/Settings.php: -------------------------------------------------------------------------------- 1 | scopeConfig->getValue(self::IS_CRON_ENABLED); 35 | } 36 | 37 | /** 38 | * @return int 39 | */ 40 | public function getConfigDaysToClean() 41 | { 42 | return $this->scopeConfig->getValue(self::CONFIG_DAYS_TO_CLEAN); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getDeleteNotEmpyRedirect() 49 | { 50 | return $this->scopeConfig->getValue(self::DELETE_NOT_EMPTY_REDIRECT); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Helper/UrlCleanUp.php: -------------------------------------------------------------------------------- 1 | connection = $this->resourceConnection->getConnection(); 35 | } 36 | 37 | /** 38 | * Get days to clean 39 | * @return int 40 | */ 41 | public function getDaysToClean($days = null): int 42 | { 43 | if ($days) { 44 | return (int)$days; 45 | } 46 | 47 | return (int)$this->settings->getConfigDaysToClean(); 48 | } 49 | 50 | /** 51 | * execute cleanup 52 | * @return int 53 | */ 54 | public function execute($days = null): int 55 | { 56 | $where = ("last_visited < '" . date('c', time() - ($this->getDaysToClean($days) * (3600 * 24))) . "'"); 57 | 58 | if(!$this->settings->getDeleteNotEmpyRedirect()) 59 | $where .= (' AND to_url IS NULL'); 60 | 61 | $deletionCount = $this->connection->delete(self::TABLE, $where); 62 | 63 | $this->logger->info(__('Experius 404 url Cleanup: Removed %1 records.', $deletionCount)); 64 | 65 | return $deletionCount; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /MANUAL.md: -------------------------------------------------------------------------------- 1 | # General 2 | You could see all 404 errors in the Magento admin 3 | 4 | System > Tools > 404 Reports 5 | 6 | This view displays 5 columns: 7 | 8 | * ID (for internal usage) 9 | * 404 Url (could be an actual 404 url or an expected 404 url) 10 | * Redirect To (url to forward to) 11 | * 404 Count (number of visits) 12 | * Action (edit or remove redirect) 13 | 14 | # Example 15 | 16 | 17 | 18 | # Add an redirect manually 19 | When you'r expecting a 404 result (for disabled products or pages) you could add a redirect. 20 | 21 | Example: 22 | 23 | _Page https://www.webshop.nl/cookies will be removed and be replaced by page https://www.webshop.nl/voorwaarden_ 24 | 25 | ## Steps: 26 | 27 | * Open the 404 Reports table 28 | * Click"Add new Redirect" 29 | * Add the full old to the field "404 Url". In this case, that url would be "https://www.webshop.nl/cookies" 30 | * Add the full new url to the field "Redirect To". In this case, that url would be "https://www.webshop.nl/voorwaarden" 31 | * Click "Save Redirect" 32 | * Open the Magento front-end and navigate to "https://www.webshop.nl/cookies" 33 | * You would be redirected (301 Redirect) to "https://www.webshop.nl/voorwaarden" 34 | 35 | | DUTCH VERSION | 36 | 37 | # General 38 | Om het overzicht te zien van alle 404 errors die hebben plaatsgevonden, ga in de Magento admin naar 39 | 40 | System > Tools > 404 Reports 41 | 42 | Dit overzicht toont 5 kolommen 43 | 44 | * ID (voor intern gebruik van de module) 45 | * 404 Url (de URL waar de 404 error heeft plaatsgevonden of gaat plaatsvinden) 46 | * Redirect To (de URL waar de bezoeker naar wordt doorverwezen als hij de 404 Url bezoekt) 47 | * 404 Count (het aantal keer dat deze 404 Url is bezocht) 48 | * Action (De redirect bewerken of verwijderen) 49 | 50 | # Voorbeeld 51 | 52 | 53 | 54 | # Handmatig een redirect toevoegen 55 | Wanneer je verwacht dat er een 404 zal gaan plaatsvinden (bijvoorbeeld een CMS pagina of product zal worden verwijderd) kan er al een 301 Redirect worden ingesteld. 56 | 57 | Voorbeeld: 58 | 59 | _De pagina https://www.webshop.nl/cookies zal worden verwijderd en worden vervangen door de pagina https://www.webshop.nl/voorwaarden_ 60 | 61 | ## Stappen: 62 | 63 | * Open de 404 Reports overzicht 64 | * Klik op "Add new Redirect" 65 | * Vul bij "404 Url" de volledige URL in van de pagina die komt te vervallen, in dit geval "https://www.webshop.nl/cookies" 66 | * Vul bij "Redirect To" de volledige URL in van de pagina waar naar moet worden doorverwezen, in dit geval "https://www.webshop.nl/voorwaarden" 67 | * Klik op "Save Redirect" 68 | * Open de voorkant van de webshop en ga naar "https://www.webshop.nl/cookies" 69 | * Je wordt nu omgeleid (een 301 Redirect) naar "https://www.webshop.nl/voorwaarden" 70 | 71 | -------------------------------------------------------------------------------- /Model/PageNotFound.php: -------------------------------------------------------------------------------- 1 | _init('Experius\PageNotFound\Model\ResourceModel\PageNotFound'); 17 | } 18 | 19 | /** 20 | * Get page_not_found_id 21 | * @return string 22 | */ 23 | public function getPageNotFoundId() 24 | { 25 | return $this->getData(self::PAGE_NOT_FOUND_ID); 26 | } 27 | 28 | /** 29 | * Set page_not_found_id 30 | * @param string $pageNotFoundId 31 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 32 | */ 33 | public function setPageNotFoundId($pageNotFoundId) 34 | { 35 | return $this->setData(self::PAGE_NOT_FOUND_ID, $pageNotFoundId); 36 | } 37 | 38 | /** 39 | * Get from_url 40 | * @return string 41 | */ 42 | public function getFromUrl() 43 | { 44 | return $this->getData(self::FROM_URL); 45 | } 46 | 47 | /** 48 | * Set from_url 49 | * @param string $from_url 50 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 51 | */ 52 | public function setFromUrl($from_url) 53 | { 54 | return $this->setData(self::FROM_URL, $from_url); 55 | } 56 | 57 | /** 58 | * Get to_url 59 | * @return string 60 | */ 61 | public function getToUrl() 62 | { 63 | return $this->getData(self::TO_URL); 64 | } 65 | 66 | /** 67 | * Set to_url 68 | * @param string $to_url 69 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 70 | */ 71 | public function setToUrl($to_url) 72 | { 73 | return $this->setData(self::TO_URL, $to_url); 74 | } 75 | 76 | /** 77 | * Get count 78 | * @return int 79 | */ 80 | public function getCount() 81 | { 82 | return $this->getData(self::COUNT); 83 | } 84 | 85 | /** 86 | * Set count 87 | * @param int $count 88 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 89 | */ 90 | public function setCount($count) 91 | { 92 | return $this->setData(self::COUNT, $count); 93 | } 94 | 95 | /** 96 | * Get count_redirect 97 | * @return int 98 | */ 99 | public function getCountRedirect() 100 | { 101 | return $this->getData(self::COUNT_REDIRECT); 102 | } 103 | 104 | /** 105 | * Set count_redirect 106 | * @param int $count_redirect 107 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 108 | */ 109 | public function setCountRedirect($count_redirect) 110 | { 111 | return $this->setData(self::COUNT_REDIRECT, $count_redirect); 112 | } 113 | 114 | /** 115 | * Get last_visited 116 | * @return string 117 | */ 118 | public function getLastVisited() 119 | { 120 | return $this->getData(self::LAST_VISITED); 121 | } 122 | 123 | /** 124 | * Set last_visited 125 | * @param string $last_visited 126 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 127 | */ 128 | public function setLastVisited($last_visited) 129 | { 130 | return $this->setData(self::LAST_VISITED, $last_visited); 131 | } 132 | /** 133 | * Get last_visited 134 | * @return int 135 | */ 136 | public function getStoreId() 137 | { 138 | return $this->getData(self::LAST_VISITED); 139 | } 140 | 141 | /** 142 | * Set store_id 143 | * @param int $store_id 144 | * @return \Experius\PageNotFound\Api\Data\PageNotFoundInterface 145 | */ 146 | public function setStoreId($store_id) 147 | { 148 | return $this->setData(self::STORE_ID, $store_id); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Model/PageNotFoundRepository.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 60 | $this->pageNotFoundFactory = $pageNotFoundFactory; 61 | $this->pageNotFoundCollectionFactory = $pageNotFoundCollectionFactory; 62 | $this->searchResultsFactory = $searchResultsFactory; 63 | $this->dataObjectHelper = $dataObjectHelper; 64 | $this->dataPageNotFoundFactory = $dataPageNotFoundFactory; 65 | $this->dataObjectProcessor = $dataObjectProcessor; 66 | $this->storeManager = $storeManager; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function save( 73 | \Experius\PageNotFound\Api\Data\PageNotFoundInterface $pageNotFound 74 | ) { 75 | /* if (empty($pageNotFound->getStoreId())) { 76 | $storeId = $this->storeManager->getStore()->getId(); 77 | $pageNotFound->setStoreId($storeId); 78 | } */ 79 | try { 80 | $pageNotFound->getResource()->save($pageNotFound); 81 | } catch (\Exception $exception) { 82 | throw new CouldNotSaveException(__( 83 | 'Could not save the pageNotFound: %1', 84 | $exception->getMessage() 85 | )); 86 | } 87 | return $pageNotFound; 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function getById($pageNotFoundId) 94 | { 95 | $pageNotFound = $this->pageNotFoundFactory->create(); 96 | $pageNotFound->getResource()->load($pageNotFound, $pageNotFoundId); 97 | if (!$pageNotFound->getId()) { 98 | throw new NoSuchEntityException(__('page_not_found with id "%1" does not exist.', $pageNotFoundId)); 99 | } 100 | return $pageNotFound; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function getByFromUrl($pageNotFoundUrl) 107 | { 108 | $pageNotFound = $this->pageNotFoundFactory->create(); 109 | $pageNotFound->getResource()->load($pageNotFound, $pageNotFoundUrl, 'from_url'); 110 | if (!$pageNotFound->getId()) { 111 | throw new NoSuchEntityException(__('page_not_found with url "%1" does not exist.', $pageNotFoundUrl)); 112 | } 113 | return $pageNotFound; 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | public function getList( 120 | \Magento\Framework\Api\SearchCriteriaInterface $criteria 121 | ) { 122 | $collection = $this->pageNotFoundCollectionFactory->create(); 123 | foreach ($criteria->getFilterGroups() as $filterGroup) { 124 | foreach ($filterGroup->getFilters() as $filter) { 125 | if ($filter->getField() === 'store_id') { 126 | $collection->addStoreFilter($filter->getValue(), false); 127 | continue; 128 | } 129 | $condition = $filter->getConditionType() ?: 'eq'; 130 | $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); 131 | } 132 | } 133 | 134 | $sortOrders = $criteria->getSortOrders(); 135 | if ($sortOrders) { 136 | /** @var SortOrder $sortOrder */ 137 | foreach ($sortOrders as $sortOrder) { 138 | if (!$sortOrder->getField()) { 139 | continue; 140 | } 141 | $collection->addOrder( 142 | $sortOrder->getField(), 143 | ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' 144 | ); 145 | } 146 | } 147 | $collection->setCurPage($criteria->getCurrentPage()); 148 | $collection->setPageSize($criteria->getPageSize()); 149 | 150 | $searchResults = $this->searchResultsFactory->create(); 151 | $searchResults->setSearchCriteria($criteria); 152 | $searchResults->setTotalCount($collection->getSize()); 153 | $searchResults->setItems($collection->getItems()); 154 | return $searchResults; 155 | } 156 | 157 | /** 158 | * {@inheritdoc} 159 | */ 160 | public function delete( 161 | \Experius\PageNotFound\Api\Data\PageNotFoundInterface $pageNotFound 162 | ) { 163 | try { 164 | $pageNotFound->getResource()->delete($pageNotFound); 165 | } catch (\Exception $exception) { 166 | throw new CouldNotDeleteException(__( 167 | 'Could not delete the page_not_found: %1', 168 | $exception->getMessage() 169 | )); 170 | } 171 | return true; 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function deleteById($pageNotFoundId) 178 | { 179 | return $this->delete($this->getById($pageNotFoundId)); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Model/Pagenotfound/DataProvider.php: -------------------------------------------------------------------------------- 1 | collection = $collectionFactory->create(); 39 | $this->dataPersistor = $dataPersistor; 40 | parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); 41 | } 42 | 43 | /** 44 | * Get data 45 | * 46 | * @return array 47 | */ 48 | public function getData() 49 | { 50 | if (isset($this->loadedData)) { 51 | return $this->loadedData; 52 | } 53 | $items = $this->collection->getItems(); 54 | foreach ($items as $model) { 55 | $this->loadedData[$model->getId()] = $model->getData(); 56 | } 57 | $data = $this->dataPersistor->get('experius_pagenotfound_page_not_found'); 58 | 59 | if (!empty($data)) { 60 | $model = $this->collection->getNewEmptyItem(); 61 | $model->setData($data); 62 | $this->loadedData[$model->getId()] = $model->getData(); 63 | $this->dataPersistor->clear('experius_pagenotfound_page_not_found'); 64 | } 65 | 66 | return $this->loadedData; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Model/ResourceModel/PageNotFound.php: -------------------------------------------------------------------------------- 1 | _init('experius_page_not_found', 'page_not_found_id'); 17 | $this->addUniqueField(['field' => 'from_url', 'title' => __('A redirect for 404 Url already exists')]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Model/ResourceModel/PageNotFound/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 17 | 'Experius\PageNotFound\Model\PageNotFound', 18 | 'Experius\PageNotFound\Model\ResourceModel\PageNotFound' 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Observer/Controller/ActionPredispatch.php: -------------------------------------------------------------------------------- 1 | url = $url; 44 | $this->pageNotFoundFactory = $pageNotFoundFactory; 45 | $this->response = $response; 46 | $this->actionFactory = $actionFactory; 47 | $this->cacheState = $cacheState; 48 | $this->scopeConfig = $scopeConfig; 49 | $this->resultFactory = $resultFactory; 50 | $this->storeManager = $storeManager; 51 | 52 | } 53 | 54 | private function isEnabled() 55 | { 56 | $configValue = $this->scopeConfig->getValue('pagenotfound/general/enabled',\Magento\Store\Model\ScopeInterface::SCOPE_STORE); 57 | return $configValue ? explode(',',$configValue) : []; 58 | } 59 | 60 | private function includedParamsInRedirect() 61 | { 62 | $configValue = $this->scopeConfig->getValue('pagenotfound/general/included_params_redirect',\Magento\Store\Model\ScopeInterface::SCOPE_STORE); 63 | return $configValue ? explode(',',$configValue) : []; 64 | } 65 | 66 | private function includedParamsInFromUrl() 67 | { 68 | $configValue = $this->scopeConfig->getValue('pagenotfound/general/included_params_from_url',\Magento\Store\Model\ScopeInterface::SCOPE_STORE); 69 | return $configValue ? explode(',',$configValue) : []; 70 | } 71 | 72 | public function execute( 73 | \Magento\Framework\Event\Observer $observer 74 | ) { 75 | 76 | if(!$this->isEnabled()){ 77 | return; 78 | } 79 | 80 | $this->request = $observer->getRequest(); 81 | $this->action = $observer->getControllerAction(); 82 | 83 | foreach (['full_page'] as $type) { 84 | $this->cacheState->setEnabled($type, false); 85 | } 86 | 87 | $this->urlParts = parse_url($this->url->getCurrentUrl()); 88 | 89 | $this->savePageNotFound($this->getCurrentUrl()); 90 | 91 | } 92 | 93 | /* @return \Magento\Framework\App\RequestInterface */ 94 | protected function getRequest(){ 95 | return $this->request; 96 | } 97 | 98 | /* @return \Magento\Cms\Controller\Noroute\Index */ 99 | protected function getAction(){ 100 | return $this->action; 101 | } 102 | 103 | protected function getCurrentUrl(){ 104 | return $this->stripFromUrl(); 105 | } 106 | 107 | protected function stripFromUrl(){ 108 | 109 | $url_parts = $this->urlParts; 110 | 111 | // remove all params from url and add only the configured ones. 112 | $params = (!empty($this->getParams(false))) ? '?' . $this->getParams(false) : ''; 113 | $url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . $params; 114 | 115 | return $url; 116 | } 117 | 118 | /** 119 | * @param $fromUrl 120 | * @param $isGraphql 121 | * @param StoreInterface|null $store 122 | * @return array|ActionInterface|string|string[]|void 123 | * @throws \Exception 124 | */ 125 | protected function savePageNotFound($fromUrl, $isGraphql = false, StoreInterface $store = null) 126 | { 127 | /* @var $pageNotFoundModel PageNotFound */ 128 | $pageNotFoundModel = $this->pageNotFoundFactory->create(); 129 | 130 | if ($isGraphql) { 131 | // Create full url to return with GraphQL 132 | $baseUrl = $store->getBaseUrl(); 133 | if (strpos($fromUrl, $baseUrl) === false) { 134 | $fromUrl = $baseUrl . ltrim($fromUrl, '/'); 135 | } 136 | } 137 | 138 | $pageNotFoundModel->load($fromUrl, 'from_url'); 139 | $currentDate = date("Y-m-d"); 140 | $pageNotFoundModel->setLastVisited($currentDate); 141 | 142 | $pageNotFoundModel->setStoreId($this->getStoreId()); 143 | 144 | if ($pageNotFoundModel->getId() && empty($pageNotFoundModel->getToUrl())) { 145 | $count = $pageNotFoundModel->getCount(); 146 | $pageNotFoundModel->setCount($count + 1); 147 | } elseif ($pageNotFoundModel->getId() && !empty($pageNotFoundModel->getToUrl())) { 148 | $count = $pageNotFoundModel->getCount(); 149 | } else { 150 | $pageNotFoundModel->setFromUrl($fromUrl); 151 | $pageNotFoundModel->setCount(1); 152 | } 153 | 154 | if ($pageNotFoundModel->getToUrl()) { 155 | $pageNotFoundModel->setCountRedirect($pageNotFoundModel->getCountRedirect() + 1); 156 | } 157 | 158 | $pageNotFoundModel->save(); 159 | 160 | if ($pageNotFoundModel->getToUrl()) { 161 | if ($isGraphql) { 162 | return str_replace($baseUrl, '', $pageNotFoundModel->getToUrl()); 163 | } 164 | 165 | return $this->redirect($pageNotFoundModel->getToUrl(), '301'); 166 | } 167 | } 168 | 169 | /** 170 | * @SuppressWarnings(PHPMD.UnusedLocalVariable) 171 | */ 172 | protected function getParams($redirect=true){ 173 | 174 | $queryArray = $this->getRequest()->getParams(); 175 | 176 | $unsetParams = ($redirect) ? $this->includedParamsInRedirect() : $this->includedParamsInFromUrl(); 177 | 178 | foreach($queryArray as $key=>$value){ 179 | 180 | if(!in_array($key,$unsetParams) || !in_array(strtolower($key),$unsetParams)){ 181 | unset($queryArray[$key]); 182 | } 183 | 184 | } 185 | 186 | return http_build_query($queryArray); 187 | } 188 | 189 | protected function urlHasParams($url){ 190 | $urlParts = parse_url($url); 191 | if(isset($urlParts['query']) && $urlParts['query']){ 192 | return true; 193 | } 194 | return false; 195 | } 196 | 197 | /** 198 | * @param $url 199 | * @return \Magento\Framework\App\ActionInterface 200 | * @SuppressWarnings(PHPMD) 201 | */ 202 | protected function redirect($url) 203 | { 204 | 205 | if($url=='410'){ 206 | $result = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD); 207 | return $result->setModule('experius_pagenotfound')->setController('response')->forward('gone'); 208 | } else { 209 | // add all configured params to redirect url. 210 | $queryStart = ($this->urlHasParams($url)) ? '&' : '?'; 211 | $params = (!empty($this->getParams(true))) ? $queryStart . $this->getParams(true) : ''; 212 | $url = $url . $params; 213 | header("HTTP/1.1 301 Moved Permanently"); 214 | } 215 | 216 | header("Location: " . $url); 217 | // Exit usage for problems with FPC 218 | // phpcs:disable 219 | exit(); 220 | // phpcs:enable 221 | 222 | $this->response->setRedirect($url,$code); 223 | 224 | $this->getRequest()->setDispatched(true); 225 | $this->getRequest()->setParam('no_cache', true); 226 | 227 | return $this->actionFactory->create('Magento\Framework\App\Action\Redirect'); 228 | } 229 | 230 | 231 | /** 232 | * @return int 233 | */ 234 | protected function getStoreId(): int{ 235 | Return $this->storeManager->getStore()->getId() ?: 0; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magento 2 Module Experius Page Not Found 404 2 | 3 | This module saves all 404 urls to a database table. 4 | 5 | - Adds an admin grid with 404s 6 | - Adds an admin config 7 | - It includes a count so you can see which 404 urls needs your attention first. 8 | - Allows you to configure a permanent redirect for the 404s found in the admin grid. 9 | - Saves the last visited date 10 | - Saves the storeview for easy filtering 11 | 12 | Admin grid location: System > Tools > 404 Reports 13 | 14 | Admin configuration location: Stores > Settings > Configuration > Advanced > 404 Reports 15 | 16 | ## Why should you use it? 17 | 18 | 1. Reports all 404s, not only the ones from Google. 19 | 2. Store owner can fix them by them 20 | 3. Import redirect list when migrating to M2. 21 | 22 | ## Installation 23 | 24 | ```bash 25 | composer require experius/module-pagenotfound 26 | ``` 27 | 28 | ## How to use the import csv function? 29 | 30 | 1. Create a csv called "pagenotfound.csv" with two two columns: from and to url (don't add column headers) 31 | 2. Add the full url including https:// (for both from and to url) to this csv 32 | 3. Use semicolon (";") as your separator 33 | 4. Upload csv on the Magento server (f.e. var/import folder) 34 | 5. Run the import file command including the url from the previous step 35 | 36 | 37 | ## cronjob 38 | 39 | - Can be turned on and off in the admin configuration 40 | - 404 reports with a redirect can be kept 41 | - Even if it normally would've had been deleted 42 | - Runs once per day 43 | - Runs at 03:00 AM 44 | 45 | ## command 46 | 47 | - Command to delete records 48 | - Deletes records according to the config in the admin 49 | - Parameter `--days` is available to overwrite the days in the admin configuration 50 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/Link.php: -------------------------------------------------------------------------------- 1 | getData('name'); 26 | foreach ($dataSource['data']['items'] as & $item) { 27 | if(!empty($item[$fieldName])){ 28 | $javascript = 'window.open(this.href,"_blank"); return false;'; 29 | $item[$fieldName] = "".$item[$fieldName].""; 30 | } 31 | } 32 | } 33 | 34 | return $dataSource; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/PagenotfoundActions.php: -------------------------------------------------------------------------------- 1 | urlBuilder = $urlBuilder; 29 | parent::__construct($context, $uiComponentFactory, $components, $data); 30 | } 31 | 32 | /** 33 | * Prepare Data Source 34 | * 35 | * @param array $dataSource 36 | * @return array 37 | */ 38 | public function prepareDataSource(array $dataSource) 39 | { 40 | if (isset($dataSource['data']['items'])) { 41 | foreach ($dataSource['data']['items'] as & $item) { 42 | if (isset($item['page_not_found_id'])) { 43 | $item[$this->getData('name')] = [ 44 | 'edit' => [ 45 | 'href' => $this->urlBuilder->getUrl( 46 | static::URL_PATH_EDIT, 47 | [ 48 | 'page_not_found_id' => $item['page_not_found_id'] 49 | ] 50 | ), 51 | 'label' => __('Edit') 52 | ], 53 | 'delete' => [ 54 | 'href' => $this->urlBuilder->getUrl( 55 | static::URL_PATH_DELETE, 56 | [ 57 | 'page_not_found_id' => $item['page_not_found_id'] 58 | ] 59 | ), 60 | 'label' => __('Delete'), 61 | 'confirm' => [ 62 | 'title' => __('Delete "${ $.$data.title }"'), 63 | 'message' => __('Are you sure you wan\'t to delete a "${ $.$data.title }" record?') 64 | ] 65 | ] 66 | ]; 67 | } 68 | } 69 | } 70 | 71 | return $dataSource; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "experius/module-pagenotfound", 3 | "description": "", 4 | "license": "proprietary", 5 | "authors": [ 6 | { 7 | "name": "Mage2Gen", 8 | "email": "info@mage2gen.com" 9 | }, 10 | { 11 | "name": "Derrick Heesbeen", 12 | "email": "derrick@experius.nl" 13 | }, 14 | { 15 | "name": "Simon Vianen", 16 | "email": "simonvianen@experius.nl" 17 | } 18 | ], 19 | "minimum-stability": "dev", 20 | "require": {}, 21 | "autoload": { 22 | "psr-4": { 23 | "Experius\\PageNotFound\\": "" 24 | }, 25 | "files": [ 26 | "registration.php" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Magento\Framework\Data\OptionSourceInterface 8 | Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface 9 | Magento\Framework\View\Element\UiComponent\ContextInterface 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | advanced 7 | Experius_PageNotFound::config_experius_pagenotfound 8 | 9 | 10 | 11 | 12 | 13 | Magento\Config\Model\Config\Source\Yesno 14 | 15 | 16 | 17 | comma separated. For example language,p,id 18 | 19 | 20 | 21 | comma separated. For example language,p,id 22 | 23 | 24 | 25 | 26 | 27 | 28 | cronjob runs once per day 29 | Magento\Config\Model\Config\Source\Yesno 30 | 31 | 32 | 33 | 34 | 35 | 36 | delete rows with filled 'redirect to' 37 | Magento\Config\Model\Config\Source\Yesno 38 | 39 | 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | 11 | 0 12 | 90 13 | 0 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0 3 * * * 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/db_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 11 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /etc/db_schema_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "experius_page_not_found": { 3 | "column": { 4 | "page_not_found_id": true, 5 | "from_url": true, 6 | "to_url": true, 7 | "count": true, 8 | "count_redirect": true 9 | }, 10 | "constraint": { 11 | "PRIMARY": true 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | experius_page_not_found 9 | Experius\PageNotFound\Model\ResourceModel\PageNotFound\Collection 10 | 11 | 12 | 13 | 14 | 15 | Experius\PageNotFound\Model\ResourceModel\PageNotFound\Grid\Collection 16 | 17 | 18 | 19 | 20 | 21 | 22 | Experius\PageNotFound\Console\Command\Import 23 | Experius\PageNotFound\Console\Command\Clean 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /etc/frontend/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /view/adminhtml/layout/experius_pagenotfound_pagenotfound_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /view/adminhtml/layout/experius_pagenotfound_pagenotfound_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/experius_page_not_found_form.xml: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | experius_page_not_found_form.page_not_found_form_data_source 6 | experius_page_not_found_form.page_not_found_form_data_source 7 | 8 | General Information 9 | 10 | data 11 | experius_page_not_found_form 12 | 13 | templates/form/collapsible 14 | 15 | Experius\PageNotFound\Block\Adminhtml\Page\Not\Found\Edit\BackButton 16 | Experius\PageNotFound\Block\Adminhtml\Page\Not\Found\Edit\DeleteButton 17 | Experius\PageNotFound\Block\Adminhtml\Page\Not\Found\Edit\SaveButton 18 | Experius\PageNotFound\Block\Adminhtml\Page\Not\Found\Edit\SaveAndContinueButton 19 | 20 | 21 | 22 | 23 | Experius\PageNotFound\Model\Pagenotfound\DataProvider 24 | page_not_found_form_data_source 25 | page_not_found_id 26 | page_not_found_id 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Magento_Ui/js/form/provider 36 | 37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | text 49 | 404 Url 50 | input 51 | page_not_found 52 | 10 53 | from_url 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | text 65 | Redirect To 66 | input 67 | page_not_found 68 | 20 69 | to_url 70 | 71 | false 72 | 73 | 74 | 75 | 76 |
77 |
78 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/experius_page_not_found_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | experius_page_not_found_index.experius_page_not_found_grid_data_source 6 | experius_page_not_found_index.experius_page_not_found_grid_data_source 7 | 8 | experius_page_not_found_columns 9 | 10 | 11 | add 12 | Add new Redirect 13 | primary 14 | */*/new 15 | 16 | 17 | 18 | 19 | 20 | Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider 21 | experius_page_not_found_grid_data_source 22 | page_not_found_id 23 | id 24 | 25 | 26 | Magento_Ui/js/grid/provider 27 | 28 | 29 | page_not_found_id 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | experius_page_not_found_index.experius_page_not_found_index.experius_page_not_found_columns_editor 61 | startEdit 62 | 63 | ${ $.$data.rowIndex } 64 | true 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | page_not_found_id 74 | 75 | 76 | 77 | 78 | 79 | 80 | text 81 | ID 82 | 83 | 84 | 85 | 86 | 87 | 88 | text 89 | 404 Url 90 | 91 | text 92 | 93 | false 94 | 95 | 96 | ui/grid/cells/html 97 | 98 | 99 | 100 | 101 | 102 | 103 | text 104 | Redirect To 105 | 106 | text 107 | 108 | false 109 | 110 | 111 | ui/grid/cells/html 112 | 113 | 114 | 115 | 116 | 117 | Magento\Cms\Ui\Component\Listing\Column\Cms\Options 118 | 119 | select 120 | Store view 121 | select 122 | 123 | select 124 | 125 | ui/grid/cells/html 126 | 127 | 128 | 129 | 130 | 131 | 132 | text 133 | 404 Count 134 | 135 | 136 | 137 | 138 | 139 | 140 | text 141 | Redirect Count 142 | 143 | 144 | 145 | 146 | 147 | dateRange 148 | date 149 | yyyy-MM-dd 150 | 151 | 152 | 153 | 154 | 155 | 156 | page_not_found_id 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /view/frontend/layout/experius_pagenotfound_response_gone.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /view/frontend/templates/response/gone.phtml: -------------------------------------------------------------------------------- 1 | 6 |
7 |
escapeHtml(__('The page you requested was not found, and we have a fine guess why.')); ?>
8 |
9 |
    10 |
  • escapeHtml(__('If you typed the URL directly, please make sure the spelling is correct.')); ?>
  • 11 |
  • escapeHtml(__('If you clicked on a link to get here, the link is outdated.')); ?>
  • 12 |
13 |
14 |
--------------------------------------------------------------------------------