1<?php 2/* 3 * Gallery - a web based photo album viewer and editor 4 * Copyright (C) 2000-2008 Bharat Mediratta 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or (at 9 * your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, but 12 * WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21/** 22 * This view lets you make very simple callbacks to the framework to get very specific data. 23 * Eventually this will probably get refactored into a much more sophisticated framework. 24 * 25 * @package GalleryCore 26 * @subpackage UserInterface 27 * @author Bharat Mediratta <bharat@menalto.com> 28 * @version $Revision: 17580 $ 29 */ 30class PluginCallbackView extends GalleryView { 31 /** 32 * @see GalleryView::isImmediate 33 */ 34 function isImmediate() { 35 return true; 36 } 37 38 /** 39 * @see GalleryView::isControllerLike 40 */ 41 function isControllerLike() { 42 return true; 43 } 44 45 /** 46 * @see GalleryView::renderImmediate 47 */ 48 function renderImmediate($status, $error) { 49 global $gallery; 50 $session =& $gallery->getSession(); 51 $storage =& $gallery->getStorage(); 52 53 $command = GalleryUtilities::getRequestVariables('command'); 54 if (!headers_sent()) { 55 header("Content-type: text/plain; charset=UTF-8"); 56 } 57 58 $result = array(); 59 list ($ret, $beforeStates) = $this->getPluginStates(); 60 if ($ret) { 61 $result['status'] = 'error'; 62 $storage->rollbackTransaction(); /* ignore errors here */ 63 $ret->putInSession(); 64 } 65 66 if (!$ret) { 67 $ret = $this->handleCallback($command, $result); 68 if ($ret) { 69 $result['status'] = 'error'; 70 $storage->rollbackTransaction(); /* ignore errors here */ 71 $ret->putInSession(); 72 } else { 73 /* Make sure this change is isolated from any potential failures we get below. */ 74 $ret = $storage->checkPoint(); 75 if ($ret) { 76 $result['status'] = 'error'; 77 $ret->putInSession(); 78 } 79 } 80 81 if ($result['status'] == 'redirect') { 82 $urlGenerator =& $gallery->getUrlGenerator(); 83 $result['redirect'] = 84 $urlGenerator->generateUrl($result['redirect'], 85 array('htmlEntities' => 0, 'forceServerRelativeUrl' => 1)); 86 } 87 } 88 89 if (!$ret) { 90 list ($ret, $afterStates) = $this->getPluginStates(); 91 if ($ret) { 92 $result['status'] = 'error'; 93 $storage->rollbackTransaction(); /* ignore errors here */ 94 $ret->putInSession(); 95 } else { 96 $result = array_merge( 97 $result, $this->calculateStateChanges($beforeStates, $afterStates)); 98 } 99 } 100 101 GalleryCoreApi::requireOnce('lib/JSON/JSON.php'); 102 $json = new Services_JSON(); 103 print $json->encode($result); 104 return null; 105 } 106 107 /** 108 * Given two sets of states, figure out what's changed from before to after. 109 * 110 * @param array $beforeStates (moduleId => state, ...) 111 * @param array $afterStates (moduleId => state, ...) 112 * @return array changed states (moduleId => state, ...) 113 * @static 114 */ 115 function calculateStateChanges($beforeStates, $afterStates) { 116 $states = array(); 117 $deleted = array(); 118 foreach (array('module', 'theme') as $type) { 119 foreach ($beforeStates[$type] as $moduleId => $state) { 120 if (!isset($afterStates[$type][$moduleId])) { 121 $deleted[$type][$moduleId] = 1; 122 } else if ($afterStates[$type][$moduleId] != $state) { 123 $states[$type][$moduleId] = $afterStates[$type][$moduleId]; 124 } 125 } 126 } 127 return array('states' => $states, 'deleted' => $deleted); 128 } 129 130 /** 131 * Handle the specific callback, and store its result in the given output array. 132 * 133 * @param string $command (eg. "installModule") 134 * @param array $result the location for result data to be sent back to the browser 135 * @return GalleryStatus a status code 136 * @static 137 */ 138 function handleCallback($command, &$result) { 139 global $gallery; 140 $platform =& $gallery->getPlatform(); 141 142 $ret = GalleryCoreApi::assertUserIsSiteAdministrator(); 143 if ($ret) { 144 return $ret; 145 } 146 147 $result = array(); 148 list ($pluginType, $pluginId) = 149 GalleryUtilities::getRequestVariables('pluginType', 'pluginId'); 150 151 list ($ret, $plugin) = GalleryCoreApi::loadPlugin($pluginType, $pluginId, true); 152 if ($ret) { 153 return $ret; 154 } 155 156 list ($ret, $isActive) = $plugin->isActive(); 157 if ($ret) { 158 return $ret; 159 } 160 161 switch($command) { 162 case 'activate': 163 164 if ($pluginType == 'module') { 165 list ($ret, $needsConfiguration) = $plugin->needsConfiguration(); 166 if ($ret) { 167 return $ret; 168 } 169 } else { 170 /* Themes don't need configuration */ 171 $needsConfiguration = false; 172 } 173 174 if ($isActive || $needsConfiguration) { 175 /* UI shouldn't let us come here anyway */ 176 $result['status'] = 'invalid'; 177 return null; 178 } 179 180 list ($ret, $redirect) = $plugin->activate(); 181 if ($ret) { 182 return $ret; 183 } 184 185 if ($redirect) { 186 $result['status'] = 'redirect'; 187 $result['redirect'] = $redirect; 188 } else { 189 $result['status'] = 'success'; 190 } 191 break; 192 193 case 'deactivate': 194 if (!$isActive) { 195 /* UI shouldn't let us come here anyway */ 196 $result['status'] = 'invalid'; 197 return null; 198 } 199 200 if ($pluginType == 'theme') { 201 list ($ret, $defaultThemeId) = GalleryCoreApi::getPluginParameter( 202 'module', 'core', 'default.theme'); 203 if ($ret) { 204 return $ret; 205 } 206 207 if ($plugin->getId() == $defaultThemeId) { 208 /* UI shouldn't let us come here anyway */ 209 $result['status'] = 'invalid'; 210 } 211 } 212 213 if (empty($result['status'])) { 214 list ($ret, $redirect) = $plugin->deactivate(); 215 if ($ret) { 216 return $ret; 217 } 218 219 if ($redirect) { 220 $result['status'] = 'redirect'; 221 $result['redirect'] = $redirect; 222 } else { 223 $result['status'] = 'success'; 224 } 225 } 226 break; 227 228 case 'upgrade': 229 case 'install': 230 $ret = $plugin->installOrUpgrade(); 231 if ($ret) { 232 return $ret; 233 } 234 235 if ($pluginType == 'module') { 236 list ($ret, $autoConfigured) = $plugin->autoConfigure(); 237 if ($ret) { 238 return $ret; 239 } 240 } else { 241 /* Themes don't need this step */ 242 $autoConfigured = true; 243 } 244 245 246 if ($autoConfigured) { 247 list ($ret, $redirect) = $plugin->activate(); 248 if ($ret) { 249 if ($ret->getErrorCode() & ERROR_CONFIGURATION_REQUIRED) { 250 /* 251 * Some modules don't override autoConfigure which defaults to success. 252 * Show the "Modules needs configuration" message. 253 */ 254 } else { 255 return $ret; 256 } 257 } 258 259 if ($redirect) { 260 $result['status'] = 'redirect'; 261 $result['redirect'] = $redirect; 262 } else { 263 $result['status'] = 'success'; 264 } 265 } else { 266 $result['status'] = 'success'; 267 } 268 269 break; 270 271 case 'uninstall': 272 if ($isActive) { 273 list ($ret, $redirect) = $plugin->deactivate(); 274 if ($ret) { 275 return $ret; 276 } 277 } else { 278 $redirect = false; 279 } 280 281 if ($redirect) { 282 $result['status'] = 'redirect'; 283 $results['redirect'] = $redirect; 284 } else { 285 $ret = $plugin->uninstall(); 286 if ($ret) { 287 return $ret; 288 } 289 $result['status'] = 'success'; 290 } 291 break; 292 293 case 'delete': 294 if ($isActive) { 295 list ($ret, $redirect) = $plugin->deactivate(); 296 if ($ret) { 297 return $ret; 298 } 299 } else { 300 $redirect = false; 301 } 302 303 if ($redirect) { 304 $result['status'] = 'redirect'; 305 $results['redirect'] = $redirect; 306 } else { 307 $ret = $plugin->uninstall(); 308 if ($ret) { 309 return $ret; 310 } 311 312 $path = sprintf( 313 "%s%ss/%s", GalleryCoreApi::getCodeBasePath(), $pluginType, $pluginId); 314 $success = @$platform->recursiveRmdir($path); 315 if (!$success) { 316 $result['status'] = 'fail'; 317 } else { 318 $ret = GalleryCoreApi::removeMapEntry( 319 'GalleryPluginPackageMap', 320 array('pluginType' => $pluginType, 'pluginId' => $pluginId)); 321 if ($ret) { 322 return $ret; 323 } 324 325 $result['status'] = 'success'; 326 } 327 } 328 break; 329 330 case 'configure': 331 $result['status'] = 'redirect'; 332 $result['redirect'] = array('view' => 'core.SiteAdmin', 333 'subView' => $plugin->getConfigurationView()); 334 break; 335 } 336 337 return null; 338 } 339 340 /** 341 * Get the state ('active', 'inactive', 'uninstalled', etc) of all modules 342 * 343 * @return array GalleryStatus a status code 344 * array(moduleId => state, ...) 345 * @static 346 */ 347 function getPluginStates() { 348 $states = array(); 349 350 foreach (array('module', 'theme') as $type) { 351 list ($ret, $pluginStatus) = GalleryCoreApi::fetchPluginStatus($type, true); 352 if ($ret) { 353 return array($ret, null); 354 } 355 foreach ($pluginStatus as $pluginId => $status) { 356 list ($ret, $plugin) = GalleryCoreApi::loadPlugin($type, $pluginId, true); 357 if ($ret) { 358 return array($ret, null); 359 } 360 361 list ($ret, $states[$type][$pluginId]) = 362 $this->getPluginState($type, $plugin, $status); 363 if ($ret) { 364 return array($ret, null); 365 } 366 } 367 } 368 369 return array(null, $states); 370 } 371 372 /** 373 * Get the state ('active', 'inactive', 'uninstalled', etc) of a given module 374 * 375 * @param string $type ('module' or 'theme') 376 * @param GalleryPlugin $plugin 377 * @param array $status status of the plugin (from GalleryCoreApi::fetchPluginStatus) 378 * @return array GalleryStatus a status code 379 * string a state 380 * @static 381 */ 382 function getPluginState($type, $plugin, $status) { 383 if ($type == 'module' && $plugin->getId() == 'core') { 384 return array(null, 'active'); 385 } 386 387 $coreApiCompatible = GalleryUtilities::isCompatibleWithApi( 388 $plugin->getRequiredCoreApi(), GalleryCoreApi::getApiVersion()); 389 390 /* TODO: refactor this into type specific wrapper methods around getPluginState() */ 391 switch ($type) { 392 case 'module': 393 $pluginApiCompatible = GalleryUtilities::isCompatibleWithApi( 394 $plugin->getRequiredModuleApi(), GalleryModule::getApiVersion()); 395 break; 396 397 case 'theme': 398 $pluginApiCompatible = GalleryUtilities::isCompatibleWithApi( 399 $plugin->getRequiredThemeApi(), GalleryTheme::getApiVersion()); 400 break; 401 } 402 403 if ($coreApiCompatible && $pluginApiCompatible) { 404 if (empty($status['active'])) { 405 $version = $status['version']; 406 $state = 'inactive'; 407 408 /* 409 * If the database versions doesn't match the module 410 * version, we need to get the user to install the module. 411 */ 412 if ($version != $plugin->getVersion()) { 413 if (empty($version)) { 414 $state = 'uninstalled'; 415 } else { 416 $state = 'unupgraded'; 417 } 418 } else { 419 if ($type == 'module') { 420 /* 421 * The versions match, but the module can still demand 422 * to be configured before being activated. 423 */ 424 list ($ret, $needsConfig) = $plugin->needsConfiguration(); 425 if ($ret) { 426 return array($ret, null); 427 } 428 } else { 429 $needsConfig = false; 430 } 431 432 if ($needsConfig) { 433 $state = 'unconfigured'; 434 } else { 435 $state = 'inactive'; 436 } 437 } 438 } else { 439 $state = 'active'; 440 } 441 } else { 442 $state = 'incompatible'; 443 } 444 445 return array(null, $state); 446 } 447} 448?> 449