diff options
Diffstat (limited to 'modules/g2_import')
| -rw-r--r-- | modules/g2_import/controllers/admin_g2_import.php | 136 | ||||
| -rw-r--r-- | modules/g2_import/controllers/g2.php | 121 | ||||
| -rwxr-xr-x | modules/g2_import/data/broken-image.gif | bin | 0 -> 1589 bytes | |||
| -rw-r--r-- | modules/g2_import/helpers/g2_import.php | 1375 | ||||
| -rw-r--r-- | modules/g2_import/helpers/g2_import_event.php | 40 | ||||
| -rw-r--r-- | modules/g2_import/helpers/g2_import_installer.php | 50 | ||||
| -rw-r--r-- | modules/g2_import/helpers/g2_import_task.php | 225 | ||||
| -rw-r--r-- | modules/g2_import/libraries/G2_Import_Exception.php | 39 | ||||
| -rw-r--r-- | modules/g2_import/models/g2_map.php | 21 | ||||
| -rw-r--r-- | modules/g2_import/module.info | 7 | ||||
| -rw-r--r-- | modules/g2_import/views/admin_g2_import.html.php | 150 |
11 files changed, 2164 insertions, 0 deletions
diff --git a/modules/g2_import/controllers/admin_g2_import.php b/modules/g2_import/controllers/admin_g2_import.php new file mode 100644 index 0000000..c4f0390 --- /dev/null +++ b/modules/g2_import/controllers/admin_g2_import.php @@ -0,0 +1,136 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Admin_g2_import_Controller extends Admin_Controller { + public function index() { + g2_import::lower_error_reporting(); + if (g2_import::is_configured()) { + g2_import::init(); + } + + $view = new Admin_View("admin.html"); + $view->page_title = t("Gallery 2 import"); + $view->content = new View("admin_g2_import.html"); + + if (class_exists("GalleryCoreApi")) { + $view->content->g2_stats = $g2_stats = g2_import::g2_stats(); + $view->content->g3_stats = $g3_stats = g2_import::g3_stats(); + $view->content->g2_sizes = g2_import::common_sizes(); + $view->content->g2_version = g2_import::version(); + + // Don't count tags because we don't track them in g2_map + $view->content->g2_resource_count = + $g2_stats["users"] + $g2_stats["groups"] + $g2_stats["albums"] + + $g2_stats["photos"] + $g2_stats["movies"] + $g2_stats["comments"]; + $view->content->g3_resource_count = + $g3_stats["user"] + $g3_stats["group"] + $g3_stats["album"] + + $g3_stats["item"] + $g3_stats["comment"] + $g3_stats["tag"]; + } + + $view->content->form = $this->_get_import_form(); + $view->content->version = ""; + $view->content->thumb_size = module::get_var("gallery", "thumb_size"); + $view->content->resize_size = module::get_var("gallery", "resize_size"); + + if (g2_import::is_initialized()) { + if ((bool)ini_get("eaccelerator.enable") || (bool)ini_get("xcache.cacher")) { + message::warning(t("The eAccelerator and XCache PHP performance extensions are known to cause issues. If you're using either of those and are having problems, please disable them while you do your import. Add the following lines: <pre>%lines</pre> to gallery3/.htaccess and remove them when the import is done.", array("lines" => "\n\n php_value eaccelerator.enable 0\n php_value xcache.cacher off\n php_value xcache.optimizer off\n\n"))); + } + + foreach (array("notification", "search", "exif") as $module_id) { + if (module::is_active($module_id)) { + message::warning( + t("<a href=\"%url\">Deactivating</a> the <b>%module_id</b> module during your import will make it faster", + array("url" => url::site("admin/modules"), "module_id" => $module_id))); + } + } + if (module::is_active("akismet")) { + message::warning( + t("The Akismet module may mark some or all of your imported comments as spam. <a href=\"%url\">Deactivate</a> it to avoid that outcome.", + array("url" => url::site("admin/modules")))); + } + } else if (g2_import::is_configured()) { + $view->content->form->configure_g2_import->embed_path->add_error("invalid", 1); + } + g2_import::restore_error_reporting(); + print $view; + } + + public function save() { + access::verify_csrf(); + g2_import::lower_error_reporting(); + + $form = $this->_get_import_form(); + if ($form->validate()) { + $embed_path = $form->configure_g2_import->embed_path->value; + if (!is_file($embed_path) && file_exists("$embed_path/embed.php")) { + $embed_path = "$embed_path/embed.php"; + } + + if (($g2_init_error = g2_import::is_valid_embed_path($embed_path)) == "ok") { + message::success(t("Gallery 2 path saved")); + module::set_var("g2_import", "embed_path", $embed_path); + url::redirect("admin/g2_import"); + } else { + $form->configure_g2_import->embed_path->add_error($g2_init_error, 1); + } + } + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_g2_import.html"); + $view->content->form = $form; + g2_import::restore_error_reporting(); + print $view; + } + + public function autocomplete() { + $directories = array(); + $path_prefix = Input::instance()->get("q"); + foreach (glob("{$path_prefix}*") as $file) { + if (is_dir($file) && !is_link($file)) { + $file = html::clean($file); + $directories[] = $file; + + // If we find an embed.php, include it as well + if (file_exists("$file/embed.php")) { + $directories[] = "$file/embed.php"; + } + } + } + + ajax::response(implode("\n", $directories)); + } + + private function _get_import_form() { + $embed_path = module::get_var("g2_import", "embed_path", ""); + $form = new Forge( + "admin/g2_import/save", "", "post", array("id" => "g-admin-configure-g2-import-form")); + $group = $form->group("configure_g2_import")->label(t("Configure Gallery 2 Import")); + $group->input("embed_path")->label(t("Filesystem path to your Gallery 2 embed.php file")) + ->value($embed_path); + $group->embed_path->error_messages( + "invalid", t("The path you entered is not a Gallery 2 installation.")); + $group->embed_path->error_messages( + "broken", t("Your Gallery 2 install isn't working properly. Please verify it!")); + $group->embed_path->error_messages( + "missing", t("The path you entered does not exist.")); + $group->submit("")->value($embed_path ? t("Change") : t("Continue")); + return $form; + } +}
\ No newline at end of file diff --git a/modules/g2_import/controllers/g2.php b/modules/g2_import/controllers/g2.php new file mode 100644 index 0000000..c24d52e --- /dev/null +++ b/modules/g2_import/controllers/g2.php @@ -0,0 +1,121 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class G2_Controller extends Controller { + /** + * Redirect Gallery 2 urls to their appropriate matching Gallery 3 url. + * + * We use mod_rewrite to create this path, so Gallery2 urls like this: + * /gallery2/v/Family/Wedding.jpg.html + * /gallery2/main.php?g2_view=core.ShowItem&g2_itemId=1234 + * + * Show up here like this: + * /g2/map?path=v/Family/Wedding.jpg.html + * /g2/map?g2_view=core.ShowItem&g2_itemId=1931 + */ + public function map() { + $input = Input::instance(); + $path = $input->get("path"); + $id = $input->get("g2_itemId"); + $view = $input->get("g2_view"); + + // Tags did not have mappings created, so we need to catch them first. However, if a g2_itemId was + // passed, we'll want to show lookup the mapping anyway + if (($path && 0 === strpos($path, "tag/")) || $view == "tags.VirtualAlbum") { + if (0 === strpos($path, "tag/")) { + $tag_name = substr($path, 4); + } + if ($view == "tags.VirtualAlbum") { + $tag_name = $input->get("g2_tagName"); + } + + if (!$id) { + url::redirect("tag_name/$tag_name", 301); + } + + $tag = ORM::factory("tag")->where("name", "=", $tag_name)->find(); + if ($tag->loaded()) { + item::set_display_context_callback("Tag_Controller::get_display_context", $tag->id); + // We want to show the item as part of the tag virtual album. Most of this code is below; we'll + // change $path and $view to let it fall through + $view = ""; + $path = ""; + } + } + + if (($path && $path != 'index.php' && $path != 'main.php') || $id) { + if ($id) { + // Requests by id are either core.DownloadItem or core.ShowItem requests. Later versions of + // Gallery 2 don't specify g2_view if it's the default (core.ShowItem). And in some cases + // (bbcode, embedding) people are using the id style URLs although URL rewriting is enabled. + $where = array(array("g2_id", "=", $id)); + if ($view == "core.DownloadItem") { + $where[] = array("resource_type", "IN", array("file", "resize", "thumbnail", "full")); + } else if ($view) { + $where[] = array("g2_url", "LIKE", "%" . Database::escape_for_like("g2_view=$view") . "%"); + } // else: Assuming that the first search hit is sufficiently good. + } else if ($path) { + $where = array(array("g2_url", "IN", array($path, str_replace(" ", "+", $path)))); + } else { + throw new Kohana_404_Exception(); + } + + $g2_map = ORM::factory("g2_map") + ->merge_where($where) + ->find(); + + if (!$g2_map->loaded()) { + throw new Kohana_404_Exception(); + } + + $item = ORM::factory("item", $g2_map->g3_id); + if (!$item->loaded()) { + throw new Kohana_404_Exception(); + } + $resource_type = $g2_map->resource_type; + } else { + $item = item::root(); + $resource_type = "album"; + } + access::required("view", $item); + + + // Redirect the user to the new url + switch ($resource_type) { + case "thumbnail": + url::redirect($item->thumb_url(true), 301); + + case "resize": + url::redirect($item->resize_url(true), 301); + + case "file": + case "full": + url::redirect($item->file_url(true), 301); + + case "item": + case "album": + url::redirect($item->abs_url(), 301); + + case "group": + case "user": + default: + throw new Kohana_404_Exception(); + } + } +} diff --git a/modules/g2_import/data/broken-image.gif b/modules/g2_import/data/broken-image.gif Binary files differnew file mode 100755 index 0000000..fb9c824 --- /dev/null +++ b/modules/g2_import/data/broken-image.gif diff --git a/modules/g2_import/helpers/g2_import.php b/modules/g2_import/helpers/g2_import.php new file mode 100644 index 0000000..82850e8 --- /dev/null +++ b/modules/g2_import/helpers/g2_import.php @@ -0,0 +1,1375 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +class g2_import_Core { + public static $init = false; + public static $map = array(); + public static $g2_base_url = null; + + private static $current_g2_item = null; + private static $error_reporting = null; + + static function is_configured() { + return module::get_var("g2_import", "embed_path"); + } + + static function is_initialized() { + return g2_import::$init == "ok"; + } + + static function init() { + if (g2_import::$init) { + return; + } + + $embed_path = module::get_var("g2_import", "embed_path"); + if (empty($embed_path)) { + throw new Exception("@todo G2_IMPORT_NOT_CONFIGURED"); + } + + g2_import::$init = g2_import::init_embed($embed_path); + } + + static function is_valid_embed_path($embed_path) { + $mod_path = VARPATH . "modules/g2_import/" . md5($embed_path); + if (file_exists($mod_path)) { + dir::unlink($mod_path); + } + return g2_import::init_embed($embed_path); + } + + /** + * Initialize the embedded Gallery 2 instance. Call this before any other Gallery 2 calls. + * + * Return values: + * "ok" - the Gallery 2 install is fine + * "missing" - the embed path does not exist + * "invalid" - the install path is not a valid Gallery 2 code base + * "broken" - the embed path is correct, but the Gallery 2 install is broken + */ + static function init_embed($embed_path) { + if (!is_file($embed_path)) { + return "missing"; + } + + try { + // Gallery 2 defines a class called Gallery. So does Gallery 3. They don't get along. So do + // a total hack here and copy over a few critical files (embed.php, main.php, bootstrap.inc + // and Gallery.class) and munge them so that we can rename the Gallery class to be + // G2_Gallery. Is this retarded? Why yes it is. + // + // Store the munged files in a directory that's the md5 hash of the embed path so that + // multiple import sources don't interfere with each other. + + $mod_path = VARPATH . "modules/g2_import/" . md5($embed_path); + if (!file_exists($mod_path) || !file_exists("$mod_path/embed.php")) { + @dir::unlink($mod_path); + mkdir($mod_path); + + $config_dir = dirname($embed_path); + if (filesize($embed_path) > 200) { + // Regular install + $base_dir = $config_dir; + } else { + // Multisite install. Line 2 of embed.php will be something like: + // require('/usr/home/bharat/public_html/gallery2/embed.php'); + $lines = file($embed_path); + preg_match("#require\('(.*)/embed.php'\);#", $lines[2], $matches); + $base_dir = $matches[1]; + } + + file_put_contents( + "$mod_path/embed.php", + str_replace( + array( + "require_once(dirname(__FILE__) . '/modules/core/classes/GalleryDataCache.class');", + "require(dirname(__FILE__) . '/modules/core/classes/GalleryEmbed.class');"), + array( + "require_once('$base_dir/modules/core/classes/GalleryDataCache.class');", + "require('$base_dir/modules/core/classes/GalleryEmbed.class');"), + array_merge( + array("<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"), + file("$base_dir/embed.php")))); + + file_put_contents( + "$mod_path/main.php", + str_replace( + array( + "include(dirname(__FILE__) . '/bootstrap.inc');", + "require_once(dirname(__FILE__) . '/init.inc');"), + array( + "include(dirname(__FILE__) . '/bootstrap.inc');", + "require_once('$base_dir/init.inc');"), + array_merge( + array("<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"), + file("$base_dir/main.php")))); + + file_put_contents( + "$mod_path/bootstrap.inc", + str_replace( + array( + "require_once(dirname(__FILE__) . '/modules/core/classes/Gallery.class');", + "require_once(dirname(__FILE__) . '/modules/core/classes/GalleryDataCache.class');", + "define('GALLERY_CONFIG_DIR', dirname(__FILE__));", + "\$gallery =& new Gallery();", + "\$GLOBALS['gallery'] =& new Gallery();", + "\$gallery = new Gallery();"), + array( + "require_once(dirname(__FILE__) . '/Gallery.class');", + "require_once('$base_dir/modules/core/classes/GalleryDataCache.class');", + "define('GALLERY_CONFIG_DIR', '$config_dir');", + "\$gallery =& new G2_Gallery();", + "\$GLOBALS['gallery'] =& new G2_Gallery();", + "\$gallery = new G2_Gallery();"), + array_merge( + array("<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"), + file("$base_dir/bootstrap.inc")))); + + file_put_contents( + "$mod_path/Gallery.class", + str_replace( + array("class Gallery", + "function Gallery"), + array("class G2_Gallery", + "function G2_Gallery"), + array_merge( + array("<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"), + file("$base_dir/modules/core/classes/Gallery.class")))); + } else { + // Ok, this is a good one. If you're running a bytecode accelerator and you move your + // Gallery install, these files sometimes get cached with the wrong path and then fail to + // load properly. + // Documented in https://sourceforge.net/apps/trac/gallery/ticket/1253 + touch("$mod_path/embed.php"); + touch("$mod_path/main.php"); + touch("$mod_path/bootstrap.inc"); + touch("$mod_path/Gallery.class.inc"); + } + + require("$mod_path/embed.php"); + if (!class_exists("GalleryEmbed")) { + return "invalid"; + } + + $ret = GalleryEmbed::init(); + if ($ret) { + Kohana_Log::add("error", "Gallery 2 call failed with: " . $ret->getAsText()); + return "broken"; + } + + $admin_group_id = g2(GalleryCoreApi::getPluginParameter("module", "core", "id.adminGroup")); + $admins = g2(GalleryCoreApi::fetchUsersForGroup($admin_group_id, 1)); + $admin_id = current(array_flip($admins)); + $admin = g2(GalleryCoreApi::loadEntitiesById($admin_id)); + $GLOBALS["gallery"]->setActiveUser($admin); + + // Make sure we have an embed location so that embedded url generation comes out ok. Without + // this, the Gallery2 ModRewrite code won't try to do url generation. + $g2_embed_location = + g2(GalleryCoreApi::getPluginParameter("module", "rewrite", "modrewrite.embeddedLocation")); + + if (empty($g2_embed_location)) { + $g2_embed_location = + g2(GalleryCoreApi::getPluginParameter("module", "rewrite", "modrewrite.galleryLocation")); + g2(GalleryCoreApi::setPluginParameter("module", "rewrite", "modrewrite.embeddedLocation", + $g2_embed_location)); + g2($gallery->getStorage()->checkPoint()); + } + + if ($g2_embed_location) { + self::$g2_base_url = $g2_embed_location; + } else { + self::$g2_base_url = $GLOBALS["gallery"]->getUrlGenerator()->generateUrl( + array(), + array("forceSessionId" => false, + "htmlEntities" => false, + "urlEncode" => false, + "useAuthToken" => false)); + } + } catch (ErrorException $e) { + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + return "broken"; + } + + return "ok"; + } + + /** + * Return the version of Gallery 2 (eg "2.3") + */ + static function version() { + $core = g2(GalleryCoreApi::loadPlugin("module", "core")); + $versions = $core->getInstalledVersions(); + return $versions["gallery"]; + } + + /** + * Return true if the given Gallery 2 module is active. + */ + static function g2_module_active($module) { + static $plugin_list; + if (!$plugin_list) { + $plugin_list = g2(GalleryCoreApi::fetchPluginList("module")); + } + + return @$plugin_list[$module]["active"]; + } + + /** + * Return a set of statistics about the number of users, groups, albums, photos, movies and + * comments available for import from the Gallery 2 instance. + */ + static function g2_stats() { + global $gallery; + $root_album_id = g2(GalleryCoreApi::getDefaultAlbumId()); + $stats["users"] = g2(GalleryCoreApi::fetchUserCount()); + $stats["groups"] = g2(GalleryCoreApi::fetchGroupCount()); + $stats["albums"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryAlbumItem")); + $stats["photos"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryPhotoItem")); + $stats["movies"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryMovieItem")); + + if (g2_import::g2_module_active("comment") && module::is_active("comment")) { + GalleryCoreApi::requireOnce("modules/comment/classes/GalleryCommentHelper.class"); + list (, $stats["comments"]) = g2(GalleryCommentHelper::fetchAllComments($root_album_id, 1)); + } else { + $stats["comments"] = 0; + } + + if (g2_import::g2_module_active("tags") && module::is_active("tag")) { + $result = + g2($gallery->search("SELECT COUNT(DISTINCT([TagItemMap::itemId])) FROM [TagItemMap]")) + ->nextResult(); + $stats["tags"] = $result[0]; + } else { + $stats["tags"] = 0; + } + + return $stats; + } + + /** + * Return a set of statistics about the number of users, groups, albums, photos, movies and + * comments already imported into the Gallery 3 instance. + */ + static function g3_stats() { + $g3_stats = array( + "album" => 0, "comment" => 0, "item" => 0, "user" => 0, "group" => 0, "tag" => 0); + foreach (db::build() + ->select("resource_type") + ->select(array("C" => 'COUNT("*")')) + ->from("g2_maps") + ->where("resource_type", "IN", array("album", "comment", "item", "user", "group")) + ->group_by("resource_type") + ->execute() as $row) { + $g3_stats[$row->resource_type] = $row->C; + } + return $g3_stats; + } + + /** + * Import a single group. + */ + static function import_group(&$queue) { + $messages = array(); + $g2_group_id = array_shift($queue); + if (self::map($g2_group_id)) { + return; + } + + try { + $g2_group = g2(GalleryCoreApi::loadEntitiesById($g2_group_id)); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 group with id: %id,", + array("id" => $g2_group_id)), + $e); + } + + switch ($g2_group->getGroupType()) { + case GROUP_NORMAL: + try { + $group = identity::create_group($g2_group->getGroupName()); + $messages[] = t("Group '%name' was imported", + array("name" => $g2_group->getGroupname())); + } catch (Exception $e) { + // Did it fail because of a duplicate group name? + $group = identity::lookup_group_by_name($g2_group->getGroupname()); + if ($group) { + $messages[] = t("Group '%name' was mapped to the existing group group of the same name.", + array("name" => $g2_group->getGroupname())); + } else { + throw new G2_Import_Exception( + t("Failed to import group '%name'", + array("name" => $g2_group->getGroupname())), + $e); + } + } + + break; + + case GROUP_ALL_USERS: + $group = identity::registered_users(); + $messages[] = t("Group 'Registered' was converted to '%name'", array("name" => $group->name)); + break; + + case GROUP_SITE_ADMINS: + $messages[] = t("Group 'Admin' does not exist in Gallery 3, skipping"); + break; // This is not a group in G3 + + case GROUP_EVERYBODY: + $group = identity::everybody(); + $messages[] = t("Group 'Everybody' was converted to '%name'", array("name" => $group->name)); + break; + } + + if (isset($group)) { + self::set_map($g2_group->getId(), $group->id, "group"); + } + + return $messages; + } + + /** + * Import a single user. + */ + static function import_user(&$queue) { + $messages = array(); + $g2_user_id = array_shift($queue); + if (self::map($g2_user_id)) { + return t("User with id: %id already imported, skipping", + array("id" => $g2_user_id)); + } + + if (g2(GalleryCoreApi::isAnonymousUser($g2_user_id))) { + self::set_map($g2_user_id, identity::guest()->id, "group"); + return t("Skipping anonymous user"); + } + + $g2_admin_group_id = + g2(GalleryCoreApi::getPluginParameter("module", "core", "id.adminGroup")); + try { + $g2_user = g2(GalleryCoreApi::loadEntitiesById($g2_user_id)); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 user with id: %id\n%exception", + array("id" => $g2_user_id, "exception" => (string)$e)), + $e); + } + $g2_groups = g2(GalleryCoreApi::fetchGroupsForUser($g2_user->getId())); + + $user = identity::lookup_user_by_name($g2_user->getUsername()); + if ($user) { + $messages[] = t("Loaded existing user: '%name'.", array("name" => $user->name)); + } else { + $email = $g2_user->getEmail(); + if (empty($email) || !valid::email($email)) { + $email = "unknown@unknown.com"; + } + try { + $user = identity::create_user($g2_user->getUserName(), $g2_user->getFullName(), + // Note: The API expects a password in cleartext. + // Just use the hashed password as an unpredictable + // value here. The user will have to reset the password. + $g2_user->getHashedPassword(), $email); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to create user: '%name' (id: %id)", + array("name" => $g2_user->getUserName(), "id" => $g2_user_id)), + $e, $messages); + } + if (class_exists("User_Model") && $user instanceof User_Model) { + // This will work if G2's password is a PasswordHash password as well. + $user->hashed_password = $g2_user->getHashedPassword(); + } + $messages[] = t("Created user: '%name'.", array("name" => $user->name)); + if ($email == "unknown@unknown.com") { + $messages[] = t("Fixed invalid email (was '%invalid_email')", + array("invalid_email" => $g2_user->getEmail())); + } + } + + $user->locale = $g2_user->getLanguage(); + foreach ($g2_groups as $g2_group_id => $g2_group_name) { + if ($g2_group_id == $g2_admin_group_id) { + $user->admin = true; + $messages[] = t("Added 'admin' flag to user"); + } else { + $group = identity::lookup_group(self::map($g2_group_id)); + $user->add($group); + $messages[] = t("Added user to group '%group'.", array("group" => $group->name)); + } + } + + try { + $user->save(); + self::set_map($g2_user->getId(), $user->id, "user"); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to create user: '%name'", array("name" => $user->name)), + $e, $messages); + } + + return $messages; + } + + /** + * Import a single album. + */ + static function import_album(&$queue) { + // The queue is a set of nested associative arrays where the key is the album id and the + // value is an array of similar arrays. We'll do a breadth first tree traversal using the + // queue to keep our state. Doing it breadth first means that the parent will be created by + // the time we get to the child. + + // Dequeue the current album and enqueue its children + list($g2_album_id, $children) = each($queue); + unset($queue[$g2_album_id]); + foreach ($children as $key => $value) { + $queue[$key] = $value; + } + + if (self::map($g2_album_id)) { + return; + } + + try { + // Load the G2 album item, and figure out its parent in G3. + $g2_album = g2(GalleryCoreApi::loadEntitiesById($g2_album_id)); + } catch (Exception $e) { + return t("Failed to load Gallery 2 album with id: %id\n%exception", + array("id" => $g2_album_id, "exception" => (string)$e)); + } + + if ($g2_album->getParentId() == null) { + $album = item::root(); + } else { + $parent_album = ORM::factory("item", self::map($g2_album->getParentId())); + + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = self::map($g2_album->getParentId()); + + g2_import::set_album_values($album, $g2_album); + + try { + $album->save(); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 album with id %id and name %name.", + array("id" => $g2_album_id, "name" => $album->name)), + $e); + } + + self::import_keywords_as_tags($g2_album->getKeywords(), $album); + } + + self::set_map( + $g2_album_id, $album->id, + "album", + self::g2_url(array("view" => "core.ShowItem", "itemId" => $g2_album->getId()))); + + self::_import_permissions($g2_album, $album); + } + + /** + * Transfer over all the values from a G2 album to a G3 album. + */ + static function set_album_values($album, $g2_album) { + $album->name = $g2_album->getPathComponent(); + $album->title = self::_decode_html_special_chars($g2_album->getTitle()); + $album->title or $album->title = $album->name; + $album->description = self::_decode_html_special_chars(self::extract_description($g2_album)); + $album->owner_id = self::map($g2_album->getOwnerId()); + try { + $album->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_album->getId())); + } catch (Exception $e) { + // @todo log + $album->view_count = 0; + } + $album->created = $g2_album->getCreationTimestamp(); + + $order_map = array( + "originationTimestamp" => "captured", + "creationTimestamp" => "created", + "description" => "description", + "modificationTimestamp" => "updated", + "orderWeight" => "weight", + "pathComponent" => "name", + "summary" => "description", + "title" => "title", + "viewCount" => "view_count"); + $direction_map = array( + 1 => "ASC", + ORDER_ASCENDING => "ASC", + ORDER_DESCENDING => "DESC"); + // G2 sorts can either be <sort> or <presort>|<sort>. Right now we can't + // map presorts so ignore them. + $g2_order = explode("|", $g2_album->getOrderBy() . ""); + $g2_order = end($g2_order); + if (empty($g2_order)) { + $g2_order = g2(GalleryCoreApi::getPluginParameter('module', 'core', 'default.orderBy')); + } + $g2_order_direction = explode("|", $g2_album->getOrderDirection() . ""); + $g2_order_direction = $g2_order_direction[0]; + if (empty($g2_order_direction)) { + $g2_order_direction = + g2(GalleryCoreApi::getPluginParameter('module', 'core', 'default.orderDirection')); + } + if (array_key_exists($g2_order, $order_map)) { + $album->sort_column = $order_map[$g2_order]; + $album->sort_order = $direction_map[$g2_order_direction]; + } + } + + /** + * Set the highlight properly for a single album + */ + static function set_album_highlight(&$queue) { + // Dequeue the current album and enqueue its children + list($g2_album_id, $children) = each($queue); + unset($queue[$g2_album_id]); + if (!empty($children)) { + foreach ($children as $key => $value) { + $queue[$key] = $value; + } + } + + $messages = array(); + $g3_album_id = self::map($g2_album_id); + if (!$g3_album_id) { + return t("Album with id: %id not imported", array("id" => $g3_album_id)); + } + + $table = g2(GalleryCoreApi::fetchThumbnailsByItemIds(array($g2_album_id))); + if (isset($table[$g2_album_id])) { + // Backtrack the source id to an item + $orig_g2_source = $g2_source = $table[$g2_album_id]; + while (GalleryUtilities::isA($g2_source, "GalleryDerivative")) { + $g2_source = g2(GalleryCoreApi::loadEntitiesById($g2_source->getDerivativeSourceId())); + } + $item_id = self::map($g2_source->getId()); + if ($item_id) { + $item = ORM::factory("item", $item_id); + $g3_album = ORM::factory("item", $g3_album_id); + $g3_album->album_cover_item_id = $item->id; + $g3_album->thumb_dirty = 1; + try { + $g3_album->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_album_id)); + } catch (Exception $e) { + $g3_album->view_count = 0; + } + try { + $g3_album->save(); + graphics::generate($g3_album); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to generate an album highlight for album '%name'.", + array("name" => $g3_album->name)), + $e); + } + + self::set_map( + $orig_g2_source->getId(), $g3_album->id, + "thumbnail", + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $orig_g2_source->getId()))); + } + } + } + + /** + * Import a single photo or movie. + */ + static function import_item(&$queue) { + $g2_item_id = array_shift($queue); + + if (self::map($g2_item_id)) { + return; + } + + try { + self::$current_g2_item = $g2_item = g2(GalleryCoreApi::loadEntitiesById($g2_item_id)); + $g2_path = g2($g2_item->fetchPath()); + } catch (Exception $e) { + return t("Failed to import Gallery 2 item with id: %id\n%exception", + array("id" => $g2_item_id, "exception" => (string)$e)); + } + + $parent = ORM::factory("item", self::map($g2_item->getParentId())); + + $g2_type = $g2_item->getEntityType(); + $corrupt = 0; + if (!file_exists($g2_path)) { + // If the Gallery 2 source image isn't available, this operation is going to fail. That can + // happen in cases where there's corruption in the source Gallery 2. In that case, fall + // back on using a broken image. It's important that we import *something* otherwise + // anything that refers to this item in Gallery 2 will have a dangling pointer in Gallery 3 + // + // Note that this will change movies to be photos, if there's a broken movie. Hopefully + // this case is rare enough that we don't need to take any heroic action here. + g2_import::log( + t("%path missing in import; replacing it with a placeholder", array("path" => $g2_path))); + $g2_path = MODPATH . "g2_import/data/broken-image.gif"; + $g2_type = "GalleryPhotoItem"; + $corrupt = 1; + } + + $messages = array(); + switch ($g2_type) { + case "GalleryPhotoItem": + if (!in_array($g2_item->getMimeType(), array("image/jpeg", "image/gif", "image/png"))) { + Kohana_Log::add("alert", "$g2_path is an unsupported image type; using a placeholder gif"); + $messages[] = t("'%path' is an unsupported image type, using a placeholder", + array("path" => $g2_path)); + $g2_path = MODPATH . "g2_import/data/broken-image.gif"; + $corrupt = 1; + } + try { + $item = ORM::factory("item"); + $item->type = "photo"; + $item->parent_id = $parent->id; + $item->set_data_file($g2_path); + $item->name = $g2_item->getPathComponent(); + $item->title = self::_decode_html_special_chars($g2_item->getTitle()); + $item->title or $item->title = $item->name; + $item->description = self::_decode_html_special_chars(self::extract_description($g2_item)); + $item->owner_id = self::map($g2_item->getOwnerId()); + $item->save(); + + // If the item has a preferred derivative with a rotation, then rotate this image + // accordingly. Should we obey scale rules as well? I vote no because rotation is less + // destructive -- you lose too much data from scaling. + $g2_preferred = g2(GalleryCoreApi::fetchPreferredSource($g2_item)); + if ($g2_preferred && $g2_preferred instanceof GalleryDerivative) { + if (preg_match("/rotate\|(-?\d+)/", $g2_preferred->getDerivativeOperations(), $matches)) { + $tmpfile = tempnam(TMPPATH, "rotate"); + gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $matches[1]), $item); + $item->set_data_file($tmpfile); + $item->save(); + unlink($tmpfile); + } + } + } catch (Exception $e) { + $exception_info = (string) new G2_Import_Exception( + t("Corrupt image '%path'", array("path" => $g2_path)), + $e, $messages); + Kohana_Log::add("alert", "Corrupt image $g2_path\n" . $exception_info); + $messages[] = $exception_info; + $corrupt = 1; + $item = null; + } + break; + + case "GalleryMovieItem": + // @todo we should transcode other types into FLV + if (in_array($g2_item->getMimeType(), array("video/mp4", "video/x-flv"))) { + try { + $item = ORM::factory("item"); + $item->type = "movie"; + $item->parent_id = $parent->id; + $item->set_data_file($g2_path); + $item->name = $g2_item->getPathComponent(); + $item->title = self::_decode_html_special_chars($g2_item->getTitle()); + $item->title or $item->title = $item->name; + $item->description = self::_decode_html_special_chars(self::extract_description($g2_item)); + $item->owner_id = self::map($g2_item->getOwnerId()); + $item->save(); + } catch (Exception $e) { + $exception_info = (string) new G2_Import_Exception( + t("Corrupt movie '%path'", array("path" => $g2_path)), + $e, $messages); + Kohana_Log::add("alert", "Corrupt movie $g2_path\n" . $exception_info); + $messages[] = $exception_info; + $corrupt = 1; + $item = null; + } + } else { + Kohana_Log::add("alert", "$g2_path is an unsupported movie type"); + $messages[] = t("'%path' is an unsupported movie type", array("path" => $g2_path)); + $corrupt = 1; + } + + break; + + default: + // Ignore + break; + } + + if (!empty($item)) { + self::import_keywords_as_tags($g2_item->getKeywords(), $item); + } + + $g2_item_url = self::g2_url(array("view" => "core.ShowItem", "itemId" => $g2_item->getId())); + if (isset($item)) { + try { + $item->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_item_id)); + } catch (Exception $e) { + $view_count = 1; + } + $item->save(); + + self::set_map($g2_item_id, $item->id, "item", $g2_item_url); + + self::set_map($g2_item_id, $item->id, "file", + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $g2_item_id))); + + $derivatives = g2(GalleryCoreApi::fetchDerivativesByItemIds(array($g2_item_id))); + if (!empty($derivatives[$g2_item_id])) { + foreach ($derivatives[$g2_item_id] as $derivative) { + switch ($derivative->getDerivativeType()) { + case DERIVATIVE_TYPE_IMAGE_THUMBNAIL: $resource_type = "thumbnail"; break; + case DERIVATIVE_TYPE_IMAGE_RESIZE: $resource_type = "resize"; break; + case DERIVATIVE_TYPE_IMAGE_PREFERRED: $resource_type = "full"; break; + } + + self::set_map( + $derivative->getId(), $item->id, + $resource_type, + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $derivative->getId()))); + } + } + } + + if ($corrupt) { + if (!empty($item)) { + $title = $g2_item->getTitle(); + $title or $title = $g2_item->getPathComponent(); + $messages[] = + t("<a href=\"%g2_url\">%title</a> from Gallery 2 could not be processed; (imported as <a href=\"%g3_url\">%title</a>)", + array("g2_url" => $g2_item_url, + "g3_url" => $item->url(), + "title" => $title)); + } else { + $messages[] = + t("<a href=\"%g2_url\">%title</a> from Gallery 2 could not be processed", + array("g2_url" => $g2_item_url, "title" => $g2_item->getTitle())); + } + } + + self::$current_g2_item = null; + return $messages; + } + + /** + * g2 encoded'&', '"', '<' and '>' as '&', '"', '<' and '>' respectively. + * This function undoes that encoding. + */ + private static function _decode_html_special_chars($value) { + return str_replace(array("&", """, "<", ">"), + array("&", "\"", "<", ">"), $value); + } + + private static $_permission_map = array( + "core.view" => "view", + "core.viewSource" => "view_full", + "core.edit" => "edit", + "core.addDataItem" => "add", + "core.addAlbumItem" => "add"); + + /** + * Imports G2 permissions, mapping G2's permission model to G3's + * much simplified permissions. + * + * - Ignores user permissions, G3 only supports group permissions. + * - Ignores item permissions, G3 only supports album permissions. + * + * G2 permission -> G3 permission + * --------------------------------- + * core.view view + * core.viewSource view_full + * core.edit edit + * core.addDataItem add + * core.addAlbumItem add + * core.viewResizes <ignored> + * core.delete <ignored> + * comment.* <ignored> + */ + private static function _import_permissions($g2_album, $g3_album) { + // No need to do anything if this album has the same G2 ACL as its parent. + if ($g2_album->getParentId() != null && + g2(GalleryCoreApi::fetchAccessListId($g2_album->getId())) == + g2(GalleryCoreApi::fetchAccessListId($g2_album->getParentId()))) { + return; + } + + $granted_permissions = self::_map_permissions($g2_album->getId()); + + if ($g2_album->getParentId() == null) { + // Compare to current permissions, and change them if necessary. + $g3_parent_album = item::root(); + } else { + $g3_parent_album = $g3_album->parent(); + } + $granted_parent_permissions = array(); + $perm_ids = array_unique(array_values(self::$_permission_map)); + foreach (identity::groups() as $group) { + $granted_parent_permissions[$group->id] = array(); + foreach ($perm_ids as $perm_id) { + if (access::group_can($group, $perm_id, $g3_parent_album)) { + $granted_parent_permissions[$group->id][$perm_id] = 1; + } + } + } + + // Note: Only registering permissions if they're not the same as + // the inherited ones. + foreach ($granted_permissions as $group_id => $permissions) { + if (!isset($granted_parent_permissions[$group_id])) { + foreach (array_keys($permissions) as $perm_id) { + access::allow(identity::lookup_group($group_id), $perm_id, $g3_album); + } + } else if ($permissions != $granted_parent_permissions[$group_id]) { + $parent_permissions = $granted_parent_permissions[$group_id]; + // @todo Probably worth caching the group instances. + $group = identity::lookup_group($group_id); + // Note: Cannot use array_diff_key. + foreach (array_keys($permissions) as $perm_id) { + if (!isset($parent_permissions[$perm_id])) { + access::allow($group, $perm_id, $g3_album); + } + } + foreach (array_keys($parent_permissions) as $perm_id) { + if (!isset($permissions[$perm_id])) { + access::deny($group, $perm_id, $g3_album); + } + } + } + } + + foreach ($granted_parent_permissions as $group_id => $parent_permissions) { + if (isset($granted_permissions[$group_id])) { + continue; // handled above + } + $group = identity::lookup_group($group_id); + foreach (array_keys($parent_permissions) as $perm_id) { + access::deny($group, $perm_id, $g3_album); + } + } + } + + /** + * Loads all the granted group G2 permissions for a specific + * album and returns an array with G3 groups ids and G3 permission ids. + */ + private static function _map_permissions($g2_album_id) { + $g2_permissions = g2(GalleryCoreApi::fetchAllPermissionsForItem($g2_album_id)); + $permissions = array(); + foreach ($g2_permissions as $entry) { + // @todo Do something about user permissions? E.g. map G2's user albums + // to a user-specific group in G3? + if (!isset($entry["groupId"])) { + continue; + } + $g2_permission_id = $entry["permission"]; + if (!isset(self::$_permission_map[$g2_permission_id])) { + continue; + } + $group_id = self::map($entry["groupId"]); + if ($group_id == null) { + // E.g. the G2 admin group isn't mapped. + continue; + } + $permission_id = self::$_permission_map[$g2_permission_id]; + if (!isset($permissions[$group_id])) { + $permissions[$group_id] = array(); + } + $permissions[$group_id][$permission_id] = 1; + } + return $permissions; + } + + /** + * Import a single comment. + */ + static function import_comment(&$queue) { + $g2_comment_id = array_shift($queue); + + try { + $g2_comment = g2(GalleryCoreApi::loadEntitiesById($g2_comment_id)); + } catch (Exception $e) { + return t("Failed to load Gallery 2 comment with id: %id\%exception", + array("id" => $g2_comment_id, "exception" => (string)$e)); + } + + if ($id = self::map($g2_comment->getId())) { + if (ORM::factory("comment", $id)->loaded()) { + // Already imported and still exists + return; + } + // This comment was already imported, but now it no longer exists. Import it again, per + // ticket #1736. + } + + $item_id = self::map($g2_comment->getParentId()); + if (empty($item_id)) { + // Item was not mapped. + return; + } + + $text = join("\n", array($g2_comment->getSubject(), $g2_comment->getComment())); + $text = html_entity_decode($text); + + // Just import the fields we know about. Do this outside of the comment API for now so that + // we don't trigger spam filtering events + $comment = ORM::factory("comment"); + $comment->author_id = self::map($g2_comment->getCommenterId()); + $comment->guest_name = ""; + if ($comment->author_id == identity::guest()->id) { + $comment->guest_name = $g2_comment->getAuthor(); + $comment->guest_name or $comment->guest_name = (string) t("Anonymous coward"); + $comment->guest_email = "unknown@nobody.com"; + } + $comment->item_id = $item_id; + $comment->text = self::_transform_bbcode($text); + $comment->state = "published"; + $comment->server_http_host = $g2_comment->getHost(); + try { + $comment->save(); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to import comment with id: %id.", + array("id" => $g2_comment_id)), + $e); + } + + self::set_map($g2_comment->getId(), $comment->id, "comment"); + + // Backdate the creation date. We can't do this at creation time because + // Comment_Model::save() will override it. Leave the updated date alone + // so that if the comments get marked as spam, they don't immediately get + // flushed (see ticket #1736) + db::update("comments") + ->set("created", $g2_comment->getDate()) + ->where("id", "=", $comment->id) + ->execute(); + } + + /** + * Import all the tags for a single item + */ + static function import_tags_for_item(&$queue) { + if (!module::is_active("tag")) { + return t("Gallery 3 tag module is inactive, no tags will be imported"); + } + + GalleryCoreApi::requireOnce("modules/tags/classes/TagsHelper.class"); + $g2_item_id = array_shift($queue); + $g3_item = ORM::factory("item", self::map($g2_item_id)); + if (!$g3_item->loaded()) { + return; + } + + try { + $tag_names = array_values(g2(TagsHelper::getTagsByItemId($g2_item_id))); + } catch (Exception $e) { + return t("Failed to import Gallery 2 tags for item with id: %id\n%exception", + array("id" => $g2_item_id, "exception" => (string)$e)); + } + + foreach ($tag_names as $tag_name) { + tag::add($g3_item, $tag_name); + } + + // Tag operations are idempotent so we don't need to map them. Which is good because we don't + // have an id for each individual tag mapping anyway so it'd be hard to set up the mapping. + } + + static function import_keywords_as_tags($keywords, $item) { + // Keywords in G2 are free form. So we don't know what our user used as a separator. Try to + // be smart about it. If we see a comma or a semicolon, expect the keywords to be separated + // by that delimeter. Otherwise, use space as the delimiter. + if (strpos($keywords, ";")) { + $delim = ";"; + } else if (strpos($keywords, ",")) { + $delim = ","; + } else { + $delim = " "; + } + + foreach (preg_split("/$delim/", $keywords) as $keyword) { + $keyword = trim($keyword); + if ($keyword) { + tag::add($item, $keyword); + } + } + } + + /** + * If the thumbnails and resizes created for the Gallery 2 photo match the dimensions of the + * ones we expect to create for Gallery 3, then copy the files over instead of recreating them. + */ + static function copy_matching_thumbnails_and_resizes($item) { + // We only operate on items that are being imported + if (empty(self::$current_g2_item)) { + return; + } + + // Precaution: if the Gallery 2 item was watermarked, or we have the Gallery 3 watermark module + // active then we'd have to do something a lot more sophisticated here. For now, just skip + // this step in those cases. + // @todo we should probably use an API here, eventually. + if (module::is_active("watermark") && module::get_var("watermark", "name")) { + return; + } + + // For now just do the copy for photos and movies. Albums are tricky because we're may not + // yet be setting their album cover properly. + // @todo implement this for albums also + if (!$item->is_movie() && !$item->is_photo()) { + return; + } + + $g2_item_id = self::$current_g2_item->getId(); + $derivatives = g2(GalleryCoreApi::fetchDerivativesByItemIds(array($g2_item_id))); + + $target_thumb_size = module::get_var("gallery", "thumb_size"); + $target_resize_size = module::get_var("gallery", "resize_size"); + if (!empty($derivatives[$g2_item_id])) { + foreach ($derivatives[$g2_item_id] as $derivative) { + if ($derivative->getPostFilterOperations()) { + // Let's assume for now that this is a watermark operation, which we can't handle. + continue; + } + + if ($derivative->getDerivativeType() == DERIVATIVE_TYPE_IMAGE_THUMBNAIL && + $item->thumb_dirty && + ($derivative->getWidth() == $target_thumb_size || + $derivative->getHeight() == $target_thumb_size)) { + if (@copy(g2($derivative->fetchPath()), $item->thumb_path())) { + $item->thumb_height = $derivative->getHeight(); + $item->thumb_width = $derivative->getWidth(); + $item->thumb_dirty = 0; + } + } + + if ($derivative->getDerivativeType() == DERIVATIVE_TYPE_IMAGE_RESIZE && + $item->resize_dirty && + ($derivative->getWidth() == $target_resize_size || + $derivative->getHeight() == $target_resize_size)) { + if (@copy(g2($derivative->fetchPath()), $item->resize_path())) { + $item->resize_height = $derivative->getHeight(); + $item->resize_width = $derivative->getWidth(); + $item->resize_dirty = 0; + } + } + } + } + try { + $item->save(); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to copy thumbnails and resizes for item '%name' (Gallery 2 id: %id)", + array("name" => $item->name, "id" => $g2_item_id)), + $e); + } + } + + /** + * Figure out the most common resize and thumb sizes in Gallery 2 so that we can tell the admin + * what theme settings to set to make the import go faster. If we match up the sizes then we + * can just copy over derivatives instead of running graphics toolkit operations. + */ + static function common_sizes() { + global $gallery; + foreach (array("resize" => DERIVATIVE_TYPE_IMAGE_RESIZE, + "thumb" => DERIVATIVE_TYPE_IMAGE_THUMBNAIL) as $type => $g2_enum) { + $results = g2($gallery->search( + "SELECT COUNT(*) AS c, [GalleryDerivativeImage::width] " . + "FROM [GalleryDerivativeImage], [GalleryDerivative] " . + "WHERE [GalleryDerivativeImage::id] = [GalleryDerivative::id] " . + " AND [GalleryDerivative::derivativeType] = ? " . + " AND [GalleryDerivativeImage::width] >= [GalleryDerivativeImage::height] " . + "GROUP BY [GalleryDerivativeImage::width] " . + "ORDER by c DESC", + array($g2_enum), + array("limit" => array(1)))); + $row = $results->nextResult(); + $sizes[$type] = array("size" => $row[1], "count" => $row[0]); + + $results = g2($gallery->search( + "SELECT COUNT(*) AS c, [GalleryDerivativeImage::height] " . + "FROM [GalleryDerivativeImage], [GalleryDerivative] " . + "WHERE [GalleryDerivativeImage::id] = [GalleryDerivative::id] " . + " AND [GalleryDerivative::derivativeType] = ? " . + " AND [GalleryDerivativeImage::height] > [GalleryDerivativeImage::width] " . + "GROUP BY [GalleryDerivativeImage::height] " . + "ORDER by c DESC", + array($g2_enum), + array("limit" => array(1)))); + $row = $results->nextResult(); + // Compare the counts. If the best fitting height does not match the best fitting width, + // then pick the one with the largest count. Otherwise, sum them. + if ($sizes[$type]["size"] != $row[1]) { + if ($row[0] > $sizes[$type]["count"]) { + $sizes[$type] = array("size" => $row[1], "count" => $row[0]); + } + } else { + $sizes[$type]["count"] += $row[0]; + } + + $results = g2($gallery->search( + "SELECT COUNT(*) FROM [GalleryDerivative] WHERE [GalleryDerivative::derivativeType] = ?", + array($g2_enum))); + $row = $results->nextResult(); + $sizes[$type]["total"] = $row[0]; + } + + return $sizes; + } + + /** + * Sensibly concatenate Gallery 2 summary and descriptions into a single field. + */ + static function extract_description($g2_item) { + // If the summary is a subset of the description just import the description, else import both. + $g2_summary = $g2_item->getSummary(); + $g2_description = $g2_item->getDescription(); + if (!$g2_summary || + $g2_summary == $g2_description || + strstr($g2_description, $g2_summary) !== false) { + $description = $g2_description; + } else { + $description = $g2_summary . " " . $g2_description; + } + return self::_transform_bbcode($description); + } + + static $bbcode_mappings = array( + "#\\[b\\](.*?)\\[/b\\]#" => "<b>$1</b>", + "#\\[i\\](.*?)\\[/i\\]#" => "<i>$1</i>", + "#\\[u\\](.*?)\\[/u\\]#" => "<u>$1</u>", + "#\\[s\\](.*?)\\[/s\\]#" => "<s>$1</s>", + "#\\[url\\](.*?)\[/url\\]#" => "<a href=\"$1\">$1</a>", + "#\\[url=(.*?)\\](.*?)\[/url\\]#" => "<a href=\"$1\">$2</a>", + "#\\[img\\](.*?)\\[/img\\]#" => "<img src=\"$1\"/>", + "#\\[quote\\](.*?)\\[/quote\\]#" => "<blockquote><p>$1</p></blockquote>", + "#\\[code\\](.*?)\\[/code\\]#" => "<pre>$1</pre>", + "#\\[size=([^\\[]*)\\]([^\\[]*)\\[/size\\]#" => "<font size=\"$1\">$2</font>", + "#\\[color=([^\\[]*)\\]([^\\[]*)\\[/color\\]#" => "<font color=\"$1\">$2/font>", + "#\\[ul\\](.*?)\\/ul\\]#" => "<ul>$1</ul>", + "#\\[li\\](.*?)\\[/li\\]#" => "<li>$1</li>", + ); + private static function _transform_bbcode($text) { + if (strpos($text, "[") !== false) { + $text = preg_replace(array_keys(self::$bbcode_mappings), array_values(self::$bbcode_mappings), + $text); + } + return $text; + } + + /** + * Get a set of photo and movie ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of photos/movies to import. + */ + static function get_item_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryItem::id] " . + "FROM [GalleryEntity], [GalleryItem] " . + "WHERE [GalleryEntity::id] = [GalleryItem::id] " . + "AND [GalleryEntity::entityType] IN ('GalleryPhotoItem', 'GalleryMovieItem') " . + "AND [GalleryItem::id] > ? " . + "ORDER BY [GalleryItem::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of comment ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of comments to import. + */ + static function get_comment_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryComment::id] " . + "FROM [GalleryComment] " . + "WHERE [GalleryComment::publishStatus] = 0 " . // 0 == COMMENT_PUBLISH_STATUS_PUBLISHED + "AND [GalleryComment::id] > ? " . + "ORDER BY [GalleryComment::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of comment ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of comments to import. + */ + static function get_tag_item_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT DISTINCT([TagItemMap::itemId]) FROM [TagItemMap] " . + "WHERE [TagItemMap::itemId] > ? " . + "ORDER BY [TagItemMap::itemId] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of user ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of users to import. + */ + static function get_user_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryUser::id] " . + "FROM [GalleryUser] " . + "WHERE [GalleryUser::id] > ? " . + "ORDER BY [GalleryUser::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of group ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of groups to import. + */ + static function get_group_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryGroup::id] " . + "FROM [GalleryGroup] " . + "WHERE [GalleryGroup::id] > ? " . + "ORDER BY [GalleryGroup::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Look in our map to find the corresponding Gallery 3 id for the given Gallery 2 id. + */ + static function map($g2_id) { + if (!array_key_exists($g2_id, self::$map)) { + $g2_map = ORM::factory("g2_map")->where("g2_id", "=", $g2_id)->find(); + self::$map[$g2_id] = $g2_map->loaded() ? $g2_map->g3_id : null; + } + + return self::$map[$g2_id]; + } + + /** + * Associate a Gallery 2 id with a Gallery 3 item id. + */ + static function set_map($g2_id, $g3_id, $resource_type, $g2_url=null) { + self::clear_map($g2_id, $resource_type); + $g2_map = ORM::factory("g2_map"); + $g2_map->g3_id = $g3_id; + $g2_map->g2_id = $g2_id; + $g2_map->resource_type = $resource_type; + + if (strpos($g2_url, self::$g2_base_url) === 0) { + $g2_url = substr($g2_url, strlen(self::$g2_base_url)); + } + + $g2_map->g2_url = $g2_url; + $g2_map->save(); + self::$map[$g2_id] = $g3_id; + } + + /** + * Remove all map entries associated with the given Gallery 2 id. + */ + static function clear_map($g2_id, $resource_type) { + db::build() + ->delete("g2_maps") + ->where("g2_id", "=", $g2_id) + ->where("resource_type", "=", $resource_type) + ->execute(); + } + + static function log($msg) { + message::warning($msg); + Kohana_Log::add("alert", $msg); + } + + static function g2_url($params) { + global $gallery; + return $gallery->getUrlGenerator()->generateUrl( + $params, + array("forceSessionId" => false, + "htmlEntities" => false, + "urlEncode" => false, + "useAuthToken" => false)); + } + + static function lower_error_reporting() { + // Gallery 2 was not designed to run in E_STRICT mode and will barf out errors. So dial down + // the error reporting when we make G2 calls. + self::$error_reporting = error_reporting(error_reporting() & ~E_STRICT); + } + + static function restore_error_reporting() { + error_reporting(self::$error_reporting); + } +} + +/** + * Wrapper around Gallery 2 calls. We expect the first response to be a GalleryStatus object. If + * it's not null, then throw an exception. Strip the GalleryStatus object out of the result and + * if there's only an array of 1 return value, turn it into a scalar. This allows us to simplify + * this pattern: + * list ($ret, $foo) = GalleryCoreApi::someCall(); + * if ($ret) { handle_error(); } + * + * to: + * $foo = g2(GalleryCoreApi::someCall()); + */ +function g2() { + $args = func_get_arg(0); + $ret = is_array($args) ? array_shift($args) : $args; + if ($ret) { + Kohana_Log::add("error", "Gallery 2 call failed with: " . $ret->getAsText()); + throw new Exception("@todo G2_FUNCTION_FAILED"); + } + if (count($args) == 1) { + return $args[0]; + } else { + return $args; + } +} diff --git a/modules/g2_import/helpers/g2_import_event.php b/modules/g2_import/helpers/g2_import_event.php new file mode 100644 index 0000000..b985281 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_event.php @@ -0,0 +1,40 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class g2_import_event_Core { + static function item_deleted($item) { + db::build() + ->delete("g2_maps") + ->where("g3_id", "=", $item->id) + ->execute(); + } + + static function item_created($item) { + g2_import::copy_matching_thumbnails_and_resizes($item); + } + + static function admin_menu($menu, $theme) { + $menu + ->get("settings_menu") + ->append(Menu::factory("link") + ->id("g2_import") + ->label(t("Gallery 2 import")) + ->url(url::site("admin/g2_import"))); + } +} diff --git a/modules/g2_import/helpers/g2_import_installer.php b/modules/g2_import/helpers/g2_import_installer.php new file mode 100644 index 0000000..c756981 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_installer.php @@ -0,0 +1,50 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class g2_import_installer { + static function install() { + $db = Database::instance(); + $db->query("CREATE TABLE IF NOT EXISTS {g2_maps} ( + `id` int(9) NOT NULL auto_increment, + `g2_id` int(9) NOT NULL, + `g3_id` int(9) NOT NULL, + `g2_url` varchar(255) default NULL, + `resource_type` varchar(64) default NULL, + PRIMARY KEY (`id`), + KEY `g2_url` (`g2_url`), + KEY `g2_id` (`g2_id`)) + DEFAULT CHARSET=utf8;"); + + mkdir(VARPATH . "modules/g2_import"); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("ALTER TABLE {g2_maps} ADD COLUMN `g2_url` VARCHAR(255)"); + $db->query("ALTER TABLE {g2_maps} ADD COLUMN `resource_type` VARCHAR(64)"); + $db->query("ALTER TABLE {g2_maps} ADD KEY `g2_url` (`g2_url`)"); + module::set_version("g2_import", $version = 2); + } + } + + static function uninstall() { + @dir::unlink(VARPATH . "modules/g2_import"); + } +} diff --git a/modules/g2_import/helpers/g2_import_task.php b/modules/g2_import/helpers/g2_import_task.php new file mode 100644 index 0000000..07eacc4 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_task.php @@ -0,0 +1,225 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class g2_import_task_Core { + static function available_tasks() { + $version = ''; + g2_import::lower_error_reporting(); + if (g2_import::is_configured()) { + g2_import::init(); + // Guard from common case where the import has been + // completed and the original files have been removed. + if (class_exists("GalleryCoreApi")) { + $version = g2_import::version(); + } + } + g2_import::restore_error_reporting(); + + if (g2_import::is_initialized()) { + return array(Task_Definition::factory() + ->callback("g2_import_task::import") + ->name(t("Import from Gallery 2")) + ->description( + t("Gallery %version detected", array("version" => $version))) + ->severity(log::SUCCESS)); + } + + return array(); + } + + static function import($task) { + g2_import::lower_error_reporting(); + + $start = microtime(true); + g2_import::init(); + + $stats = $task->get("stats"); + $done = $task->get("done"); + $total = $task->get("total"); + $completed = $task->get("completed"); + $mode = $task->get("mode"); + $queue = $task->get("queue"); + if (!isset($mode)) { + $stats = g2_import::g2_stats(); + $stats["items"] = $stats["photos"] + $stats["movies"]; + unset($stats["photos"]); + unset($stats["movies"]); + $stats["highlights"] = $stats["albums"]; + $task->set("stats", $stats); + + $task->set("total", $total = array_sum(array_values($stats))); + $completed = 0; + $mode = 0; + + $done = array(); + foreach (array_keys($stats) as $key) { + $done[$key] = 0; + } + $task->set("done", $done); + + // Ensure G2 ACLs are compacted to speed up import. + g2(GalleryCoreApi::compactAccessLists()); + } + + $modes = array("groups", "users", "albums", "items", "comments", "tags", "highlights", "done"); + while (!$task->done && microtime(true) - $start < 1.5) { + if ($done[$modes[$mode]] == $stats[$modes[$mode]]) { + // Nothing left to do for this mode. Advance. + $mode++; + $task->set("last_id", 0); + $queue = array(); + + // Start the loop from the beginning again. This way if we get to a mode that requires no + // actions (eg, if the G2 comments module isn't installed) we won't try to do any comments + // queries.. in the next iteration we'll just skip over that mode. + if ($modes[$mode] != "done") { + continue; + } + } + + switch($modes[$mode]) { + case "groups": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_group_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_group($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing groups (%count of %total)", + array("count" => $done["groups"] + 1, "total" => $stats["groups"])); + break; + + case "users": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_user_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_user($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing users (%count of %total)", + array("count" => $done["users"] + 1, "total" => $stats["users"])); + break; + + case "albums": + if (empty($queue)) { + $g2_root_id = g2(GalleryCoreApi::getDefaultAlbumId()); + $tree = g2(GalleryCoreApi::fetchAlbumTree()); + $task->set("queue", $queue = array($g2_root_id => $tree)); + + // Update the root album to reflect the Gallery2 root album. + $root_album = item::root(); + g2_import::set_album_values( + $root_album, g2(GalleryCoreApi::loadEntitiesById($g2_root_id))); + $root_album->save(); + } + $log_message = g2_import::import_album($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing albums (%count of %total)", + array("count" => $done["albums"] + 1, "total" => $stats["albums"])); + break; + + case "items": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_item_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_item($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing photos (%count of %total)", + array("count" => $done["items"] + 1, "total" => $stats["items"])); + break; + + case "comments": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_comment_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_comment($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing comments (%count of %total)", + array("count" => $done["comments"] + 1, "total" => $stats["comments"])); + + break; + + case "tags": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_tag_item_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_tags_for_item($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing tags (%count of %total)", + array("count" => $done["tags"] + 1, "total" => $stats["tags"])); + + break; + + case "highlights": + if (empty($queue)) { + $task->set("queue", $queue = g2(GalleryCoreApi::fetchAlbumTree())); + } + $log_message = g2_import::set_album_highlight($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Album highlights (%count of %total)", + array("count" => $done["highlights"] + 1, "total" => $stats["highlights"])); + + break; + + case "done": + $task->status = t("Import complete"); + $task->done = true; + $task->state = "success"; + break; + } + + if (!$task->done) { + $done[$modes[$mode]]++; + $completed++; + } + } + + $task->percent_complete = 100 * ($completed / $total); + $task->set("completed", $completed); + $task->set("mode", $mode); + $task->set("queue", $queue); + $task->set("done", $done); + + g2_import::restore_error_reporting(); + } +} diff --git a/modules/g2_import/libraries/G2_Import_Exception.php b/modules/g2_import/libraries/G2_Import_Exception.php new file mode 100644 index 0000000..7732cfd --- /dev/null +++ b/modules/g2_import/libraries/G2_Import_Exception.php @@ -0,0 +1,39 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * A wrapper for exceptions to report more details in case + * it's a ORM validation exception. + */ +class G2_Import_Exception extends Exception { + public function __construct($message, Exception $previous=null, $additional_messages=null) { + if ($additional_messages) { + $message .= "\n" . implode("\n", $additional_messages); + } + if ($previous && $previous instanceof ORM_Validation_Exception) { + $message .= "\nORM validation errors: " . print_r($previous->validation->errors(), true); + } + if ($previous) { + $message .= "\n" . (string) $previous; + } + // The $previous parameter is supported in PHP 5.3.0+. + parent::__construct($message); + } +}
\ No newline at end of file diff --git a/modules/g2_import/models/g2_map.php b/modules/g2_import/models/g2_map.php new file mode 100644 index 0000000..5fb566c --- /dev/null +++ b/modules/g2_import/models/g2_map.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class G2_Map_Model_Core extends ORM { +} diff --git a/modules/g2_import/module.info b/modules/g2_import/module.info new file mode 100644 index 0000000..32af27d --- /dev/null +++ b/modules/g2_import/module.info @@ -0,0 +1,7 @@ +name = "Gallery 2 Import" +description = "Import your Gallery 2 content into Gallery 3" +version = 2 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:g2_import" +discuss_url = "http://galleryproject.org/forum_module_g2_import" diff --git a/modules/g2_import/views/admin_g2_import.html.php b/modules/g2_import/views/admin_g2_import.html.php new file mode 100644 index 0000000..22e19f5 --- /dev/null +++ b/modules/g2_import/views/admin_g2_import.html.php @@ -0,0 +1,150 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= $theme->css("jquery.autocomplete.css") ?> +<?= $theme->script("jquery.autocomplete.js") ?> +<script type="text/javascript"> +$("document").ready(function() { + $("form input[name=embed_path]").gallery_autocomplete( + "<?= url::site("__ARGS__") ?>".replace("__ARGS__", "admin/g2_import/autocomplete"), + { + max: 256, + loadingClass: "g-loading-small", + }); +}); +</script> + +<div id="g-admin-g2-import" class="g-block"> + <h1> <?= t("Gallery 2 import") ?> </h1> + <p> + <?= t("Import your Gallery 2 users, photos, movies, comments and tags into your new Gallery 3 installation.") ?> + </p> + + <script type="text/javascript"> + $(document).ready(function() { + $("#g-admin-g2-import-tabs").tabs() + <? if (!isset($g2_version)): ?> + .tabs("disable", 1) + .tabs("disable", 2) + <? elseif ($g3_resource_count > .9 * $g2_resource_count): ?> + .tabs("select", 2) + <? else: ?> + .tabs("select", 1) + <? endif ?> + ; + + // Show the tabs after the page has loaded to prevent Firefox from rendering the + // unstyled page and then flashing. + $("#g-admin-g2-import-tabs").show(); + }); + </script> + <div id="g-admin-g2-import-tabs" class="g-block-content" style="display: none"> + <ul> + <li> + <a href="#g-admin-g2-import-configure"><?= t("1. Configure Gallery2 path") ?></a> + </li> + <li> + <a href="#g-admin-g2-import-import"><?= t("2. Import!") ?></a> + </li> + <li> + <a href="#g-admin-g2-import-notes"><?= t("3. After your import") ?></a> + </li> + </ul> + <div id="g-admin-g2-import-configure" class="g-block-content"> + <?= $form ?> + </div> + <div id="g-admin-g2-import-import"> + <? if (isset($g2_version)): ?> + <ul> + <li> + <?= t("Gallery version %version detected", array("version" => $g2_version)) ?> + </li> + <? if ($g2_sizes["thumb"]["size"] && $thumb_size != $g2_sizes["thumb"]["size"]): ?> + <li> + <?= t("Your most common thumbnail size in Gallery 2 is %g2_pixels pixels, but your Gallery 3 thumbnail size is set to %g3_pixels pixels. <a href=\"%url\">Using the same value</a> will speed up your import.", + array("g2_pixels" => $g2_sizes["thumb"]["size"], + "g3_pixels" => $thumb_size, + "url" => html::mark_clean(url::site("admin/theme_options")))) ?> + </li> + <? endif ?> + + <? if ($g2_sizes["resize"]["size"] && $resize_size != $g2_sizes["resize"]["size"]): ?> + <li> + <?= t("Your most common intermediate size in Gallery 2 is %g2_pixels pixels, but your Gallery 3 intermediate size is set to %g3_pixels pixels. <a href=\"%url\">Using the same value</a> will speed up your import.", + array("g2_pixels" => $g2_sizes["resize"]["size"], + "g3_pixels" => $resize_size, + "url" => html::mark_clean(url::site("admin/theme_options")))) ?> + </li> + <? endif ?> + + <li> + <? + $t = array(); + $t[] = t2("1 user", "%count users", $g2_stats["users"]); + $t[] = t2("1 group", "%count groups", $g2_stats["groups"]); + $t[] = t2("1 album", "%count albums", $g2_stats["albums"]); + $t[] = t2("1 photo", "%count photos/movies", $g2_stats["photos"] + $g2_stats["movies"]); + $t[] = t2("1 comment", "%count comments", $g2_stats["comments"]); + $t[] = t2("1 tagged photo/movie/album", "%count tagged photos/movies/albums", + $g2_stats["tags"]); + ?> + <?= t("Your Gallery 2 has the following importable data in it: %t0, %t1, %t2, %t3, %t4, %t5", + array("t0" => $t[0], "t1" => $t[1], "t2" => $t[2], + "t3" => $t[3], "t4" => $t[4], "t5" => $t[5])) ?> + </li> + + <? if ($g3_resource_count): ?> + <li> + <? + $t = array(); + $t[] = t2("1 user", "%count users", $g3_stats["user"]); + $t[] = t2("1 group", "%count groups", $g3_stats["group"]); + $t[] = t2("1 album", "%count albums", $g3_stats["album"]); + $t[] = t2("1 photo/movie", "%count photos/movies", $g3_stats["item"]); + $t[] = t2("1 comment", "%count comments", $g3_stats["comment"]); + $t[] = t2("1 tagged photo/movie/album", "%count tagged photos/movies/albums", $g3_stats["tag"]); + ?> + <?= t("It looks like you've imported the following Gallery 2 data already: %t0, %t1, %t2, %t3, %t4, %t5", + array("t0" => $t[0], "t1" => $t[1], "t2" => $t[2], + "t3" => $t[3], "t4" => $t[4], "t5" => $t[5])) ?> + </li> + <? endif ?> + </ul> + <p> + <a class="g-button g-dialog-link ui-state-default ui-corner-all" + href="<?= url::site("admin/maintenance/start/g2_import_task::import?csrf=$csrf") ?>"> + <?= t("Begin import!") ?> + </a> + </p> + <? endif ?> + </div> + <div id="g-admin-g2-import-notes" class="g-text"> + <ul> + <li> + <?= t("Gallery 3 does not support per-user / per-item permissions. <b>Review permissions!</b>") ?> + </li> + <li> + <?= t("The only supported file formats are JPG, PNG and GIF, FLV and MP4. Other formats will be skipped.") ?> + </li> + <li> + <p> + <?= t("Redirecting Gallery 2 URLs once your migration is complete. Put this block at the top of your gallery2/.htaccess file and all Gallery 2 urls will be redirected to Gallery 3") ?> + </p> + + <textarea id="g-g2-redirect-rules" rows="4" cols="60"><IfModule mod_rewrite.c> + Options +FollowSymLinks + RewriteEngine On + RewriteBase <?= html::clean(g2_import::$g2_base_url) ?> + + RewriteRule ^(.*)$ <?= url::site("g2/map?path=\$1") ?> [QSA,L,R=301] + </IfModule></textarea> + <script type="text/javascript"> + $(document).ready(function() { + $("#g-g2-redirect-rules").click(function(event) { + this.select(); + }); + }); + </script> + </li> + </ul> + </div> + </div> +</div> |
