/wire/core/ModulesDuplicates.php
PHP | 357 lines | 186 code | 27 blank | 144 comment | 46 complexity | 24ef55ac8aa846e6d0be0f5cfd2d843c MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception
- <?php
- /**
- * ProcessWire Modules Duplicates
- *
- * Provides functions for managing sitautions where more than one
- * copy of the same module is intalled. This is a helper for the Modules class.
- *
- * ProcessWire 2.x
- * Copyright (C) 2015 by Ryan Cramer
- * This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
- *
- * https://processwire.com
- *
- */
- class ModulesDuplicates extends Wire {
- /**
- * Array of modules where more than one copy was found
- *
- * Associative array of 'ModuleName' => array(path1, path2, ...)
- *
- * @var array
- *
- */
- protected $duplicates = array();
- /**
- * Specifies which module file to use in cases where there is more than one
- *
- * Array of 'ModuleName' => '/path/to/file/from/pw/root/file.module'
- *
- * @var array
- *
- */
- protected $duplicatesUse = array();
- /**
- * Number of new duplicates found while loading modules
- *
- * @var int
- *
- */
- protected $numNewDuplicates = 0;
- /**
- * Return quantity of new duplicates found while loading modules
- *
- * @return int
- *
- */
- public function numNewDuplicates() {
- return $this->numNewDuplicates;
- }
- /**
- * Get the current duplicate in use (string) or null if not specified
- *
- * @param $className
- * @return string|null Pathname or null
- *
- */
- public function getCurrent($className) {
- return isset($this->duplicatesUse[$className]) ? $this->duplicatesUse[$className] : null;
- }
-
- /**
- * Does the given module class have a duplicate?
- *
- * @param string $className
- * @param string $pathname Optionally specify the duplicate to check
- * @return bool
- *
- */
- public function hasDuplicate($className, $pathname = '') {
- if(!isset($this->duplicates[$className])) return false;
- if($pathname) {
- $rootPath = $this->wire('config')->paths->root;
- if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname);
- return in_array($pathname, $this->duplicates[$className]);
- }
- return true;
- }
- /**
- * Add a duplicate to the list
- *
- * @param $className
- * @param $pathname
- * @param bool $current Is this the current one in use?
- *
- */
- public function addDuplicate($className, $pathname, $current = false) {
- if(!isset($this->duplicates[$className])) $this->duplicates[$className] = array();
- $rootPath = $this->wire('config')->paths->root;
- if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname);
- if(!in_array($pathname, $this->duplicates[$className])) {
- $this->duplicates[$className][] = $pathname;
- }
- if($current) {
- $this->duplicatesUse[$className] = $pathname;
- }
- }
- /**
- * Add multiple duplicates
- *
- * @param $className
- * @param array $files
- *
- */
- public function addDuplicates($className, array $files) {
- foreach($files as $file) {
- $this->addDuplicate($className, $file);
- }
- }
- /**
- * Add duplicates from module config data
- *
- * @param $className
- * @param array $data
- *
- */
- public function addFromConfigData($className, array $data) {
- $files = isset($data['-dups']) ? $data['-dups'] : array();
- $using = isset($data['-dups-use']) ? $data['-dups-use'] : '';
- if(count($files)) $this->addDuplicates($className, $files);
- if($using) $this->addDuplicate($className, $using, true); // set current, in-use
- }
-
- /**
- * Return a list of duplicate modules that were found
- *
- * If given a module className, the following is returned:
- *
- * Array(
- * 'files' => array(file1, file2, ...)
- * 'using' => '/path/to/file/from/pw/root/ModuleName.module' or blank if not defined
- * )
- *
- * If no className is specivied, the following is returned:
- *
- * Array(
- * 'ModuleName' => array(file1, file2, ...),
- * 'ModuleName' => array(file1, file2, ...),
- * ...and so on...
- * )
- *
- * @param string|Module|int $className Optionally return only duplicates for given module name
- *
- * @return array
- *
- */
- public function getDuplicates($className = '') {
- if(!$className) return $this->duplicates;
- $className = $this->wire('modules')->getModuleClass($className);
- $files = isset($this->duplicates[$className]) ? $this->duplicates[$className] : array();
- $using = isset($this->duplicatesUse[$className]) ? $this->duplicatesUse[$className] : '';
- $rootPath = $this->wire('config')->paths->root;
- foreach($files as $key => $file) {
- $file = rtrim($rootPath, '/') . $file;
- if(!file_exists($file)) {
- unset($files[$key]);
- }
- }
- if(count($files) > 1 && !$using) {
- $using = $this->wire('modules')->getModuleFile($className);
- $using = str_replace($rootPath, '/', $using);
- }
- if(count($files) < 2) {
- // no need to store duplicate info if only 0 or 1
- //unset($this->duplicates[$className], $this->duplicatesUse[$className]);
- $files = array();
- $using = '';
- }
- return array('files' => $files, 'using' => $using);
- }
- /**
- * For a module that has duplicates, tell it which file to use
- *
- * @param string $className
- * @param string $pathname Full path and filename to module file
- *
- * @throws WireException if given information that can't be resolved
- *
- */
- public function setUseDuplicate($className, $pathname) {
- $className = $this->wire('modules')->getModuleClass($className);
- $rootPath = $this->wire('config')->paths->root;
- if(!isset($this->duplicates[$className])) {
- throw new WireException("Module $className does not have duplicates");
- }
- $pathname = str_replace($rootPath, '/', $pathname);
- if(!in_array($pathname, $this->duplicates[$className])) {
- throw new WireException("Duplicate module pathname must be one of: " . implode(" \n", $this->duplicates[$className]));
- }
- if(!file_exists($rootPath . ltrim($pathname, '/'))) {
- throw new WireException("Duplicate module file does not exist: $pathname");
- }
- $this->duplicatesUse[$className] = $pathname;
- $configData = $this->wire('modules')->getModuleConfigData($className);
- $configData['-dups-use'] = $pathname;
- $this->wire('modules')->saveModuleConfigData($className, $configData);
- }
- /**
- * Update the database so that modules have information on their duplicates
- *
- */
- public function updateDuplicates() {
- $rootPath = $this->wire('config')->paths->root;
- // store duplicate information in each module's data field
- foreach($this->getDuplicates() as $moduleName => $files) {
- $dup = $this->getDuplicates($moduleName); // so that we also have 'using' info
- $files = $dup['files'];
- $using = $dup['using'];
- foreach($files as $key => $file) {
- // make files relative to site root, for portability
- $file = str_replace($rootPath, '/', $file);
- $files[$key] = $file;
- }
- $files = array_unique($files);
- $configData = $this->wire('modules')->getModuleConfigData($moduleName);
- if((empty($configData['-dups']) && !empty($files))
- || (empty($configData['-dups-use']) || $configData['-dups-use'] != $using)
- || (isset($configData['-dups']) && implode(' ', $configData['-dups']) != implode(' ', $files))
- ) {
- $this->duplicates[$moduleName] = $files;
- $this->duplicatesUse[$moduleName] = $using;
- $configData['-dups'] = $files;
- $configData['-dups-use'] = $using;
- $this->wire('modules')->saveModuleConfigData($moduleName, $configData);
- }
- }
- // update any modules that no longer have duplicates
- $removals = array();
- $query = $this->wire('database')->prepare("SELECT `class`, `flags` FROM modules WHERE `flags` & :flag");
- $query->bindValue(':flag', Modules::flagsDuplicate, PDO::PARAM_INT);
- $query->execute();
- while($row = $query->fetch(PDO::FETCH_NUM)) {
- list($class, $flags) = $row;
- if(empty($this->duplicates[$class])) {
- $flags = $flags & ~Modules::flagsDuplicate;
- $removals[$class] = $flags;
- }
- unset($this->duplicatesUse[$class]); // just in case
- }
- foreach($removals as $class => $flags) {
- $this->wire('modules')->setFlags($class, $flags);
- $configData = $this->wire('modules')->getModuleConfigData($class);
- unset($configData['-dups'], $configData['-dups-use']);
- $this->wire('modules')->saveModuleConfigData($class, $configData);
- }
- }
- /**
- * Record a duplicate at runtime
- *
- * @param string $basename Name of module
- * @param string $pathname Path of module
- * @param string $pathname2 Second path of module
- * @param array $installed Installed module info array
- *
- */
- public function recordDuplicate($basename, $pathname, $pathname2, &$installed) {
- $rootPath = $this->wire('config')->paths->root;
- // ensure paths start from root of PW install
- if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname);
- if(strpos($pathname2, $rootPath) === 0) $pathname2 = str_replace($rootPath, '/', $pathname2);
- // there are two copies of the module on the file system (likely one in /site/modules/ and another in /wire/modules/)
- if(!isset($this->duplicates[$basename])) {
- $this->duplicates[$basename] = array($pathname, $pathname2); // array(str_replace($rootPath, '/', $this->getModuleFile($basename)));
- $this->numNewDuplicates++;
- }
- if(!in_array($pathname, $this->duplicates[$basename])) {
- $this->duplicates[$basename][] = $pathname;
- $this->numNewDuplicates++;
- }
- if(!in_array($pathname2, $this->duplicates[$basename])) {
- $this->duplicates[$basename][] = $pathname2;
- $this->numNewDuplicates++;
- }
- if(isset($installed[$basename]['flags'])) {
- $flags = $installed[$basename]['flags'];
- } else {
- $flags = $this->wire('modules')->getFlags($basename);
- }
- if(!($installed[$basename]['flags'] & Modules::flagsDuplicate)) {
- // make database aware this module has multiple files by adding the duplicate flag
- $this->numNewDuplicates++; // trigger update needed
- $flags = $flags | Modules::flagsDuplicate;
- $this->wire('modules')->setFlags($basename, $flags);
- }
- $err = sprintf($this->_('There appear to be multiple copies of module "%s" on the file system.'), $basename) . ' ';
- $this->wire('log')->save('modules', $err);
- $user = $this->wire('user');
- if($user && $user->isSuperuser()) {
- $err .= $this->_('Please edit the module settings to tell ProcessWire which one to use:') . ' ' .
- "<a href='" . $this->wire('config')->urls->admin . 'module/edit?name=' . $basename . "'>$basename</a>";
- $this->warning($err, Notice::allowMarkup);
- }
- //$this->message("recordDuplicate($basename, $pathname) $this->numNewDuplicates"); //DEBUG
- //$this->message($this->duplicates[$basename]);//DEBUG
- }
- /**
- * Populate duplicates info into config data, when applicable
- *
- * @param $className
- * @param array $configData
- *
- * @return array Updated configData
- *
- */
- public function getDuplicatesConfigData($className, array $configData = array()) {
- // ensure original duplicates info is retained and validate that it is still current
- if(isset($this->duplicates[$className])) {
- foreach($this->duplicates[$className] as $key => $file) {
- $pathname = rtrim($this->wire('config')->paths->root, '/') . $file;
- if(!file_exists($pathname)) {
- unset($this->duplicates[$className][$key]);
- }
- }
- if(count($this->duplicates[$className]) < 2) {
- // no need to store any info for this if there's only 0 or 1
- unset($this->duplicates[$className], $this->duplicatesUse[$className], $configData['-dups'], $configData['-dups-use']);
- } else {
- $configData['-dups'] = $this->duplicates[$className];
- if(isset($this->duplicatesUse[$className])) {
- $pathname = rtrim($this->wire('config')->paths->root, '/') . $this->duplicatesUse[$className];
- if(file_exists($pathname)) {
- $configData['-dups-use'] = $this->duplicatesUse[$className];
- } else {
- unset($configData['-dups-use'], $this->duplicatesUse[$className]);
- }
- }
- }
- } else if(empty($this->duplicates[$className]) && isset($configData['-dups'])) {
- unset($configData['-dups'], $configData['-dups-use']);
- }
- return $configData;
- }
- }