Example #1
0
 public function autocomplete()
 {
     $tags = array();
     $tag_parts = explode(",", Input::instance()->get("term"));
     $tag_part = ltrim(end($tag_parts));
     $tag_list = ORM::factory("tag")->where("name", "LIKE", Database::escape_for_like($tag_part) . "%")->order_by("name", "ASC")->limit(100)->find_all();
     foreach ($tag_list as $tag) {
         $tags[] = (string) html::clean($tag->name);
     }
     ajax::response(json_encode($tags));
 }
 function fetch_between($repository_id, $first, $second, $keyword = NULL)
 {
     $statements = 'SELECT commit_id as "id",
         commit_revision as "revision",
         commit_parent as "parent",
         commit_time as "time",
         committer_name as "authorName",
         committer_account as "authorEmail",
         commit_message as "message"
         FROM commits LEFT OUTER JOIN committers ON commit_committer = committer_id
         WHERE commit_repository = $1 AND commit_reported = true';
     $values = array($repository_id);
     if ($first && $second) {
         $first_commit = $this->commit_for_revision($repository_id, $first);
         $second_commit = $this->commit_for_revision($repository_id, $second);
         $first = $first_commit['commit_time'];
         $second = $second_commit['commit_time'];
         $column_name = 'commit_time';
         if (!$first || !$second) {
             $first = $first_commit['commit_order'];
             $second = $second_commit['commit_order'];
             $column_name = 'commit_order';
         }
         $in_order = $first < $second;
         array_push($values, $in_order ? $first : $second);
         $statements .= ' AND ' . $column_name . ' >= $' . count($values);
         array_push($values, $in_order ? $second : $first);
         $statements .= ' AND ' . $column_name . ' <= $' . count($values);
     }
     if ($keyword) {
         array_push($values, '%' . Database::escape_for_like($keyword) . '%');
         $keyword_index = '$' . count($values);
         array_push($values, ltrim($keyword, 'r'));
         $revision_index = '$' . count($values);
         $statements .= "\n                AND ((committer_name LIKE {$keyword_index} OR committer_account LIKE {$keyword_index}) OR commit_revision = {$revision_index})";
     }
     $commits = $this->db->query_and_fetch_all($statements . ' ORDER BY commit_time, commit_order', $values);
     if (!is_array($commits)) {
         return NULL;
     }
     foreach ($commits as &$commit) {
         $commit['time'] = Database::to_js_time($commit['time']);
     }
     return $commits;
 }
Example #3
0
 /**
  * For items that are collections, you can specify the following additional query parameters to
  * query the collection.  You can specify them in any combination.
  *
  *   scope=direct
  *     Only return items that are immediately under this one
  *   scope=all
  *     Return items anywhere under this one
  *
  *   name=<substring>
  *     Only return items where the name contains this substring
  *
  *   random=true
  *     Return a single random item
  *
  *   type=<comma separate list of photo, movie or album>
  *     Limit the type to types in this list, eg: "type=photo,movie".
  *     Also limits the types returned in the member collections (same behaviour as item_rest).
  */
 static function get($request)
 {
     $item = rest::resolve($request->url);
     access::required("view", $item);
     $p = $request->params;
     if (isset($p->random)) {
         $orm = item::random_query()->offset(0)->limit(1);
     } else {
         $orm = ORM::factory("item")->viewable();
     }
     if (empty($p->scope)) {
         $p->scope = "direct";
     }
     if (!in_array($p->scope, array("direct", "all"))) {
         throw new Rest_Exception("Bad Request", 400);
     }
     if ($p->scope == "direct") {
         $orm->where("parent_id", "=", $item->id);
     } else {
         $orm->where("left_ptr", ">", $item->left_ptr);
         $orm->where("right_ptr", "<", $item->right_ptr);
     }
     if (isset($p->name)) {
         $orm->where("name", "LIKE", "%" . Database::escape_for_like($p->name) . "%");
     }
     if (isset($p->type)) {
         $orm->where("type", "IN", explode(",", $p->type));
     }
     // Apply the item's sort order, using id as the tie breaker.
     // See Item_Model::children()
     $order_by = array($item->sort_column => $item->sort_order);
     if ($item->sort_column != "id") {
         $order_by["id"] = "ASC";
     }
     $orm->order_by($order_by);
     $result = array("url" => $request->url, "entity" => $item->as_restful_array(), "relationships" => rest::relationships("item", $item));
     if ($item->is_album()) {
         $result["members"] = array();
         foreach ($orm->find_all() as $child) {
             $result["members"][] = rest::url("item", $child);
         }
     }
     return $result;
 }
function main()
{
    $data = ensure_privileged_api_data_and_token();
    $analysis_task_id = array_get($data, 'task');
    $repository_id = array_get($data, 'repository');
    $revision = array_get($data, 'revision');
    $kind = array_get($data, 'kind');
    $commit_id_to_diassociate = array_get($data, 'commit');
    $db = connect();
    $db->begin_transaction();
    require_format('AnalysisTask', $analysis_task_id, '/^\\d+$/');
    if ($commit_id_to_diassociate) {
        require_format('Commit', $commit_id_to_diassociate, '/^\\d*$/');
        $count = $db->query_and_get_affected_rows("DELETE FROM task_commits WHERE taskcommit_task = \$1 AND taskcommit_commit = \$2", array($analysis_task_id, $commit_id_to_diassociate));
        if ($count != 1) {
            $db->rollback_transaction();
            exit_with_error('UnexpectedNumberOfAffectedRows', array('affectedRows' => $count));
        }
    } else {
        require_format('Repository', $repository_id, '/^\\d+$/');
        require_format('Kind', $kind, '/^(cause|fix)$/');
        $commit_info = array('repository' => $repository_id, 'revision' => $revision);
        $commit_rows = $db->query_and_fetch_all('SELECT commit_id FROM commits WHERE commit_repository = $1 AND commit_revision LIKE $2 LIMIT 2', array($repository_id, '%' . Database::escape_for_like($revision) . '%'));
        if (count($commit_rows) > 1) {
            $db->rollback_transaction();
            exit_with_error('AmbiguousRevision', $commit_info);
        } else {
            if (!$commit_rows) {
                $db->rollback_transaction();
                exit_with_error('CommitNotFound', $commit_info);
            }
        }
        $commit_id = $commit_rows[0]['commit_id'];
        $association = array('task' => $analysis_task_id, 'commit' => $commit_id, 'is_fix' => Database::to_database_boolean($kind == 'fix'));
        $commit_id = $db->update_or_insert_row('task_commits', 'taskcommit', array('task' => $analysis_task_id, 'commit' => $commit_id), $association, 'commit');
        if (!$commit_id) {
            $db->rollback_transaction();
            exit_with_error('FailedToAssociateCommit', $association);
        }
    }
    $db->commit_transaction();
    exit_with_success();
}
Example #5
0
 static function upgrade($version)
 {
     $db = Database::instance();
     if ($version == 1) {
         module::set_var("gallery", "date_format", "Y-M-d");
         module::set_var("gallery", "date_time_format", "Y-M-d H:i:s");
         module::set_var("gallery", "time_format", "H:i:s");
         module::set_version("gallery", $version = 2);
     }
     if ($version == 2) {
         module::set_var("gallery", "show_credits", 1);
         module::set_version("gallery", $version = 3);
     }
     if ($version == 3) {
         $db->query("CREATE TABLE {caches} (\n                 `id` varchar(255) NOT NULL,\n                 `tags` varchar(255),\n                 `expiration` int(9) NOT NULL,\n                 `cache` text,\n                 PRIMARY KEY (`id`),\n                 KEY (`tags`))\n                 DEFAULT CHARSET=utf8;");
         module::set_version("gallery", $version = 4);
     }
     if ($version == 4) {
         Cache::instance()->delete_all();
         $db->query("ALTER TABLE {caches} MODIFY COLUMN `cache` LONGBLOB");
         module::set_version("gallery", $version = 5);
     }
     if ($version == 5) {
         Cache::instance()->delete_all();
         $db->query("ALTER TABLE {caches} DROP COLUMN `id`");
         $db->query("ALTER TABLE {caches} ADD COLUMN `key` varchar(255) NOT NULL");
         $db->query("ALTER TABLE {caches} ADD COLUMN `id` int(9) NOT NULL auto_increment PRIMARY KEY");
         module::set_version("gallery", $version = 6);
     }
     if ($version == 6) {
         module::clear_var("gallery", "version");
         module::set_version("gallery", $version = 7);
     }
     if ($version == 7) {
         $groups = identity::groups();
         $permissions = ORM::factory("permission")->find_all();
         foreach ($groups as $group) {
             foreach ($permissions as $permission) {
                 // Update access intents
                 $db->query("ALTER TABLE {access_intents} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT NULL");
                 // Update access cache
                 if ($permission->name === "view") {
                     $db->query("ALTER TABLE {items} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT FALSE");
                 } else {
                     $db->query("ALTER TABLE {access_caches} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) NOT NULL DEFAULT FALSE");
                 }
             }
         }
         module::set_version("gallery", $version = 8);
     }
     if ($version == 8) {
         $db->query("ALTER TABLE {items} CHANGE COLUMN `left`  `left_ptr`  INT(9) NOT NULL;");
         $db->query("ALTER TABLE {items} CHANGE COLUMN `right` `right_ptr` INT(9) NOT NULL;");
         module::set_version("gallery", $version = 9);
     }
     if ($version == 9) {
         $db->query("ALTER TABLE {items} ADD KEY `weight` (`weight` DESC);");
         module::set_version("gallery", $version = 10);
     }
     if ($version == 10) {
         module::set_var("gallery", "image_sharpen", 15);
         module::set_version("gallery", $version = 11);
     }
     if ($version == 11) {
         $db->query("ALTER TABLE {items} ADD COLUMN `relative_url_cache` varchar(255) DEFAULT NULL");
         $db->query("ALTER TABLE {items} ADD COLUMN `slug` varchar(255) DEFAULT NULL");
         // This is imperfect since some of the slugs may contain invalid characters, but it'll do
         // for now because we don't want a lengthy operation here.
         $db->query("UPDATE {items} SET `slug` = `name`");
         // Flush all path caches because we're going to start urlencoding them.
         $db->query("UPDATE {items} SET `relative_url_cache` = NULL, `relative_path_cache` = NULL");
         module::set_version("gallery", $version = 12);
     }
     if ($version == 12) {
         if (module::get_var("gallery", "active_site_theme") == "default") {
             module::set_var("gallery", "active_site_theme", "wind");
         }
         if (module::get_var("gallery", "active_admin_theme") == "admin_default") {
             module::set_var("gallery", "active_admin_theme", "admin_wind");
         }
         module::set_version("gallery", $version = 13);
     }
     if ($version == 13) {
         // Add rules for generating our thumbnails and resizes
         Database::instance()->query("UPDATE {graphics_rules} SET `operation` = CONCAT('gallery_graphics::', `operation`);");
         module::set_version("gallery", $version = 14);
     }
     if ($version == 14) {
         $sidebar_blocks = block_manager::get_active("site_sidebar");
         if (empty($sidebar_blocks)) {
             $available_blocks = block_manager::get_available_site_blocks();
             foreach (array_keys(block_manager::get_available_site_blocks()) as $id) {
                 $sidebar_blocks[] = explode(":", $id);
             }
             block_manager::set_active("site_sidebar", $sidebar_blocks);
         }
         module::set_version("gallery", $version = 15);
     }
     if ($version == 15) {
         module::set_var("gallery", "identity_provider", "user");
         module::set_version("gallery", $version = 16);
     }
     // Convert block keys to an md5 hash of the module and block name
     if ($version == 16) {
         foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) {
             $blocks = block_manager::get_active($location);
             $new_blocks = array();
             foreach ($blocks as $block) {
                 $new_blocks[md5("{$block[0]}:{$block[1]}")] = $block;
             }
             block_manager::set_active($location, $new_blocks);
         }
         module::set_version("gallery", $version = 17);
     }
     // We didn't like md5 hashes so convert block keys back to random keys to allow duplicates.
     if ($version == 17) {
         foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) {
             $blocks = block_manager::get_active($location);
             $new_blocks = array();
             foreach ($blocks as $block) {
                 $new_blocks[random::int()] = $block;
             }
             block_manager::set_active($location, $new_blocks);
         }
         module::set_version("gallery", $version = 18);
     }
     // Rename blocks_site.sidebar to blocks_site_sidebar
     if ($version == 18) {
         $blocks = block_manager::get_active("site.sidebar");
         block_manager::set_active("site_sidebar", $blocks);
         module::clear_var("gallery", "blocks_site.sidebar");
         module::set_version("gallery", $version = 19);
     }
     // Set a default for the number of simultaneous uploads
     // Version 20 was reverted in 57adefc5baa7a2b0dfcd3e736e80c2fa86d3bfa2, so skip it.
     if ($version == 19 || $version == 20) {
         module::set_var("gallery", "simultaneous_upload_limit", 5);
         module::set_version("gallery", $version = 21);
     }
     // Update the graphics rules table so that the maximum height for resizes is 640 not 480.
     // Fixes ticket #671
     if ($version == 21) {
         $resize_rule = ORM::factory("graphics_rule")->where("id", "=", "2")->find();
         // make sure it hasn't been changed already
         $args = unserialize($resize_rule->args);
         if ($args["height"] == 480 && $args["width"] == 640) {
             $args["height"] = 640;
             $resize_rule->args = serialize($args);
             $resize_rule->save();
         }
         module::set_version("gallery", $version = 22);
     }
     // Update slug values to be legal.  We should have done this in the 11->12 upgrader, but I was
     // lazy.  Mea culpa!
     if ($version == 22) {
         foreach (db::build()->from("items")->select("id", "slug")->where(db::expr("`slug` REGEXP '[^_A-Za-z0-9-]'"), "=", 1)->execute() as $row) {
             $new_slug = item::convert_filename_to_slug($row->slug);
             if (empty($new_slug)) {
                 $new_slug = random::int();
             }
             db::build()->update("items")->set("slug", $new_slug)->set("relative_url_cache", null)->where("id", "=", $row->id)->execute();
         }
         module::set_version("gallery", $version = 23);
     }
     if ($version == 23) {
         $db->query("CREATE TABLE {failed_logins} (\n                  `id` int(9) NOT NULL auto_increment,\n                  `count` int(9) NOT NULL,\n                  `name` varchar(255) NOT NULL,\n                  `time` int(9) NOT NULL,\n                  PRIMARY KEY (`id`))\n                  DEFAULT CHARSET=utf8;");
         module::set_version("gallery", $version = 24);
     }
     if ($version == 24) {
         foreach (array("logs", "tmp", "uploads") as $dir) {
             self::_protect_directory(VARPATH . $dir);
         }
         module::set_version("gallery", $version = 25);
     }
     if ($version == 25) {
         db::build()->update("items")->set("title", db::expr("`name`"))->and_open()->where("title", "IS", null)->or_where("title", "=", "")->close()->execute();
         module::set_version("gallery", $version = 26);
     }
     if ($version == 26) {
         if (in_array("failed_logins", Database::instance()->list_tables())) {
             $db->query("RENAME TABLE {failed_logins} TO {failed_auths}");
         }
         module::set_version("gallery", $version = 27);
     }
     if ($version == 27) {
         // Set the admin area timeout to 90 minutes
         module::set_var("gallery", "admin_area_timeout", 90 * 60);
         module::set_version("gallery", $version = 28);
     }
     if ($version == 28) {
         module::set_var("gallery", "credits", "Powered by <a href=\"%url\">%gallery_version</a>");
         module::set_version("gallery", $version = 29);
     }
     if ($version == 29) {
         $db->query("ALTER TABLE {caches} ADD KEY (`key`);");
         module::set_version("gallery", $version = 30);
     }
     if ($version == 30) {
         module::set_var("gallery", "maintenance_mode", 0);
         module::set_version("gallery", $version = 31);
     }
     if ($version == 31) {
         $db->query("ALTER TABLE {modules} ADD COLUMN `weight` int(9) DEFAULT NULL");
         $db->query("ALTER TABLE {modules} ADD KEY (`weight`)");
         db::update("modules")->set("weight", db::expr("`id`"))->execute();
         module::set_version("gallery", $version = 32);
     }
     if ($version == 32) {
         $db->query("ALTER TABLE {items} ADD KEY (`left_ptr`)");
         module::set_version("gallery", $version = 33);
     }
     if ($version == 33) {
         $db->query("ALTER TABLE {access_caches} ADD KEY (`item_id`)");
         module::set_version("gallery", $version = 34);
     }
     if ($version == 34) {
         module::set_var("gallery", "visible_title_length", 15);
         module::set_version("gallery", $version = 35);
     }
     if ($version == 35) {
         module::set_var("gallery", "favicon_url", "lib/images/favicon.ico");
         module::set_version("gallery", $version = 36);
     }
     if ($version == 36) {
         module::set_var("gallery", "email_from", "*****@*****.**");
         module::set_var("gallery", "email_reply_to", "*****@*****.**");
         module::set_var("gallery", "email_line_length", 70);
         module::set_var("gallery", "email_header_separator", serialize("\n"));
         module::set_version("gallery", $version = 37);
     }
     // Changed our minds and decided that the initial value should be empty
     // But don't just reset it blindly, only do it if the value is version 37 default
     if ($version == 37) {
         $email = module::get_var("gallery", "email_from", "");
         if ($email == "*****@*****.**") {
             module::set_var("gallery", "email_from", "");
         }
         $email = module::get_var("gallery", "email_reply_to", "");
         if ($email == "*****@*****.**") {
             module::set_var("gallery", "email_reply_to", "");
         }
         module::set_version("gallery", $version = 38);
     }
     if ($version == 38) {
         module::set_var("gallery", "show_user_profiles_to", "registered_users");
         module::set_version("gallery", $version = 39);
     }
     if ($version == 39) {
         module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin");
         module::set_version("gallery", $version = 40);
     }
     if ($version == 40) {
         module::clear_var("gallery", "_cache");
         module::set_version("gallery", $version = 41);
     }
     if ($version == 41) {
         $db->query("TRUNCATE TABLE {caches}");
         $db->query("ALTER TABLE {caches} DROP INDEX `key`, ADD UNIQUE `key` (`key`)");
         module::set_version("gallery", $version = 42);
     }
     if ($version == 42) {
         $db->query("ALTER TABLE {items} CHANGE `description` `description` text DEFAULT NULL");
         module::set_version("gallery", $version = 43);
     }
     if ($version == 43) {
         $db->query("ALTER TABLE {items} CHANGE `rand_key` `rand_key` DECIMAL(11, 10)");
         module::set_version("gallery", $version = 44);
     }
     if ($version == 44) {
         $db->query("ALTER TABLE {messages} CHANGE `value` `value` text default NULL");
         module::set_version("gallery", $version = 45);
     }
     if ($version == 45) {
         // Splice the upgrade_checker block into the admin dashboard at the top
         // of the page, but under the welcome block if it's in the first position.
         $blocks = block_manager::get_active("dashboard_center");
         $index = count($blocks) && current($blocks) == array("gallery", "welcome") ? 1 : 0;
         array_splice($blocks, $index, 0, array(random::int() => array("gallery", "upgrade_checker")));
         block_manager::set_active("dashboard_center", $blocks);
         module::set_var("gallery", "upgrade_checker_auto_enabled", true);
         module::set_version("gallery", $version = 46);
     }
     if ($version == 46) {
         module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png");
         module::set_version("gallery", $version = 47);
     }
     if ($version == 47 || $version == 48) {
         // Add configuration variable to set timezone.  Defaults to the currently
         // used timezone (from PHP configuration).  Note that in v48 we were
         // setting this value incorrectly, so we're going to stomp this value for v49.
         module::set_var("gallery", "timezone", null);
         module::set_version("gallery", $version = 49);
     }
     if ($version == 49) {
         // In v49 we changed the Item_Model validation code to disallow files with two dots in them,
         // but we didn't rename any files which fail to validate, so as soon as you do anything to
         // change those files (eg. as a side effect of getting the url or file path) it fails to
         // validate.  Fix those here.  This might be slow, but if it times out it can just pick up
         // where it left off.
         foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where(db::expr("`name` REGEXP '\\\\..*\\\\.'"), "=", 1)->order_by("id", "asc")->execute() as $row) {
             set_time_limit(30);
             $item = ORM::factory("item", $row->id);
             $item->name = legal_file::smash_extensions($item->name);
             $item->save();
         }
         module::set_version("gallery", $version = 50);
     }
     if ($version == 50) {
         // In v51, we added a lock_timeout variable so that administrators could edit the time out
         // from 1 second to a higher variable if their system runs concurrent parallel uploads for
         // instance.
         module::set_var("gallery", "lock_timeout", 1);
         module::set_version("gallery", $version = 51);
     }
     if ($version == 51) {
         // In v52, we added functions to the legal_file helper that map photo and movie file
         // extensions to their mime types (and allow extension of the list by other modules).  During
         // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error
         // where they were mapped to video/mp4.  This corrects the existing items.
         db::build()->update("items")->set("mime_type", "video/x-m4v")->where("name", "REGEXP", "\\.m4v\$")->execute();
         module::set_version("gallery", $version = 52);
     }
     if ($version == 52) {
         // In v53, we added the ability to change the default time used when extracting frames from
         // movies.  Previously we hard-coded this at 3 seconds, so we use that as the default.
         module::set_var("gallery", "movie_extract_frame_time", 3);
         module::set_version("gallery", $version = 53);
     }
     if ($version == 53) {
         // In v54, we changed how we check for name and slug conflicts in Item_Model.  Previously,
         // we checked the whole filename.  As a result, "foo.jpg" and "foo.png" were not considered
         // conflicting if their slugs were different (a rare case in practice since server_add and
         // uploader would give them both the same slug "foo").  Now, we check the filename without its
         // extension.  This upgrade stanza fixes any conflicts where they were previously allowed.
         // This might be slow, but if it times out it can just pick up where it left off.
         // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one
         // conflict; "bar.jpg", "bar.png", and "bar.flv" are another)
         foreach (db::build()->select_distinct(array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))")))->select(array("C" => "COUNT(\"*\")"))->from("items")->where("type", "<>", "album")->having("C", ">", 1)->group_by("parent_base_name")->execute() as $conflict) {
             list($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2);
             $base_name_escaped = Database::escape_for_like($base_name);
             // Loop through the items for each conflict
             foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where("parent_id", "=", $parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->limit(1000000)->offset(1)->execute() as $row) {
                 set_time_limit(30);
                 $item = ORM::factory("item", $row->id);
                 $item->name = $item->name;
                 // this will force Item_Model to check for conflicts on save
                 $item->save();
             }
         }
         module::set_version("gallery", $version = 54);
     }
     if ($version == 54) {
         $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)");
         module::set_version("gallery", $version = 55);
     }
     if ($version == 55) {
         // In v56, we added the ability to change the default behavior regarding movie uploads.  It
         // can be set to "always", "never", or "autodetect" to match the previous behavior where they
         // are allowed only if FFmpeg is found.
         module::set_var("gallery", "movie_allow_uploads", "autodetect");
         module::set_version("gallery", $version = 56);
     }
     if ($version == 56) {
         // Cleanup possible instances where resize_dirty of albums or movies was set to 0.  This is
         // unlikely to have occurred, and doesn't currently matter much since albums and movies don't
         // have resize images anyway.  However, it may be useful to be consistent here going forward.
         db::build()->update("items")->set("resize_dirty", 1)->where("type", "<>", "photo")->execute();
         module::set_version("gallery", $version = 57);
     }
     if ($version == 57) {
         // In v58 we changed the Item_Model validation code to disallow files or directories with
         // backslashes in them, and we need to fix any existing items that have them.  This is
         // pretty unlikely, as having backslashes would have probably already caused other issues for
         // users, but we should check anyway.  This might be slow, but if it times out it can just
         // pick up where it left off.
         foreach (db::build()->from("items")->select("id")->where(db::expr("`name` REGEXP '\\\\\\\\'"), "=", 1)->order_by("id", "asc")->execute() as $row) {
             set_time_limit(30);
             $item = ORM::factory("item", $row->id);
             $item->name = str_replace("\\", "_", $item->name);
             $item->save();
         }
         module::set_version("gallery", $version = 58);
     }
 }
Example #6
0
 /**
  * Validate the item name.  It can return the following error messages:
  * - no_slashes: contains slashes
  * - no_backslashes: contains backslashes
  * - no_trailing_period: has a trailing period
  * - illegal_data_file_extension (non-albums only): has double, no, or illegal extension
  * - conflict: has conflicting name
  */
 public function valid_name(Validation $v, $field)
 {
     if (strpos($this->name, "/") !== false) {
         $v->add_error("name", "no_slashes");
         return;
     }
     if (strpos($this->name, "\\") !== false) {
         $v->add_error("name", "no_backslashes");
         return;
     }
     if (rtrim($this->name, ".") !== $this->name) {
         $v->add_error("name", "no_trailing_period");
         return;
     }
     if ($this->is_movie() || $this->is_photo()) {
         if (substr_count($this->name, ".") > 1) {
             // Do not accept files with double extensions, as they can
             // cause problems on some versions of Apache.
             $v->add_error("name", "illegal_data_file_extension");
         }
         $ext = pathinfo($this->name, PATHINFO_EXTENSION);
         if (!$this->loaded() && !$ext) {
             // New items must have an extension
             $v->add_error("name", "illegal_data_file_extension");
             return;
         }
         if ($this->is_photo() && !legal_file::get_photo_extensions($ext) || $this->is_movie() && !legal_file::get_movie_extensions($ext)) {
             $v->add_error("name", "illegal_data_file_extension");
         }
     }
     if ($this->is_album()) {
         if (db::build()->from("items")->where("parent_id", "=", $this->parent_id)->where("name", "=", $this->name)->merge_where($this->id ? array(array("id", "<>", $this->id)) : null)->count_records()) {
             $v->add_error("name", "conflict");
             return;
         }
     } else {
         if (preg_match("/^(.*)(\\.[^\\.\\/]*?)\$/", $this->name, $matches)) {
             $base_name = $matches[1];
         } else {
             $base_name = $this->name;
         }
         $base_name_escaped = Database::escape_for_like($base_name);
         if (db::build()->from("items")->where("parent_id", "=", $this->parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->merge_where($this->id ? array(array("id", "<>", $this->id)) : null)->count_records()) {
             $v->add_error("name", "conflict");
             return;
         }
     }
 }
Example #7
0
 /**
  * Find an item by its path.  If there's no match, return an empty Item_Model.
  * NOTE: the caller is responsible for performing security checks on the resulting item.
  *
  * In addition to $path, $var_subdir can be specified ("albums", "resizes", or "thumbs").  This
  * corresponds to the file's directory in var, which is what's used in file_proxy.  By specifying
  * this, we can be smarter about items whose formats get converted (e.g. movies that get jpg
  * thumbs).  If omitted, it defaults to "albums" which looks for identical matches between $path
  * and the item name, just like pre-v3.1 behavior.
  *
  * @param string $path
  * @param string $var_subdir
  * @return object Item_Model
  */
 static function find_by_path($path, $var_subdir = "albums")
 {
     $path = trim($path, "/");
     // The root path name is NULL not "", hence this workaround.
     if ($path == "") {
         return item::root();
     }
     $search_full_name = true;
     $album_thumb = false;
     if ($var_subdir == "thumbs" && preg_match("|^(.*)/\\.album\\.jpg\$|", $path, $matches)) {
         // It's an album thumb - remove "/.album.jpg" from the path.
         $path = $matches[1];
         $album_thumb = true;
     } else {
         if ($var_subdir != "albums" && preg_match("/^(.*)\\.jpg\$/", $path, $matches)) {
             // Item itself could be non-jpg (e.g. movies) - remove .jpg from path, don't search full name.
             $path = $matches[1];
             $search_full_name = false;
         }
     }
     // Check to see if there's an item in the database with a matching relative_path_cache value.
     // Since that field is urlencoded, we must urlencode the components of the path.
     foreach (explode("/", $path) as $part) {
         $encoded_array[] = rawurlencode($part);
     }
     $encoded_path = join("/", $encoded_array);
     if ($search_full_name) {
         $item = ORM::factory("item")->where("relative_path_cache", "=", $encoded_path)->find();
         // See if the item was found and if it should have been found.
         if ($item->loaded() && ($var_subdir == "albums" || $item->is_photo() || $album_thumb)) {
             return $item;
         }
     } else {
         // Note that the below query uses LIKE with wildcard % at end, which is still sargable and
         // therefore still takes advantage of the indexed relative_path_cache (i.e. still quick).
         $item = ORM::factory("item")->where("relative_path_cache", "LIKE", Database::escape_for_like($encoded_path) . ".%")->find();
         // See if the item was found and should be a jpg.
         if ($item->loaded() && ($item->is_movie() && $var_subdir == "thumbs" || $item->is_photo() && preg_match("/^(.*)\\.jpg\$/", $item->name))) {
             return $item;
         }
     }
     // Since the relative_path_cache field is a cache, it can be unavailable.  If we don't find
     // anything, fall back to checking the path the hard way.
     $paths = explode("/", $path);
     if ($search_full_name) {
         foreach (ORM::factory("item")->where("name", "=", end($paths))->where("level", "=", count($paths) + 1)->find_all() as $item) {
             // See if the item was found and if it should have been found.
             if (urldecode($item->relative_path()) == $path && ($var_subdir == "albums" || $item->is_photo() || $album_thumb)) {
                 return $item;
             }
         }
     } else {
         foreach (ORM::factory("item")->where("name", "LIKE", Database::escape_for_like(end($paths)) . ".%")->where("level", "=", count($paths) + 1)->find_all() as $item) {
             // Compare relative_path without extension (regexp same as legal_file::change_extension),
             // see if it should be a jpg.
             if (preg_replace("/\\.[^\\.\\/]*?\$/", "", urldecode($item->relative_path())) == $path && ($item->is_movie() && $var_subdir == "thumbs" || $item->is_photo() && preg_match("/^(.*)\\.jpg\$/", $item->name))) {
                 return $item;
             }
         }
     }
     // Nothing found - return an empty item model.
     return new Item_Model();
 }
Example #8
0
 /**
  * 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();
     }
 }
Example #9
0
 /**
  * Deletes a cache item by id or tag
  *
  * @param  string  cache id or tag, or true for "all items"
  * @param  bool    delete a tag
  * @return bool
  */
 public function delete($keys, $is_tag = false)
 {
     $db = db::build()->delete("caches");
     if ($keys === true) {
         // Delete all caches
     } else {
         if ($is_tag === true) {
             foreach ($keys as $tag) {
                 $db->where("tags", "LIKE", "%" . Database::escape_for_like("<{$tag}>") . "%");
             }
         } else {
             $db->where("key", "IN", $keys);
         }
     }
     $status = $db->execute();
     return count($status) > 0;
 }
Example #10
0
 function escape_for_like_test()
 {
     // Note: literal double backslash is written as \\\
     $this->assert_same('basic\\_test', Database::escape_for_like("basic_test"));
     $this->assert_same('\\\\100\\%\\_test/', Database::escape_for_like('\\100%_test/'));
 }
Example #11
0
 static function fix($task)
 {
     $start = microtime(true);
     $total = $task->get("total");
     if (empty($total)) {
         $item_count = db::build()->count_records("items");
         $total = 0;
         // mptt: 2 operations for every item
         $total += 2 * $item_count;
         // album audit (permissions and bogus album covers): 1 operation for every album
         $total += db::build()->where("type", "=", "album")->count_records("items");
         // one operation for each dupe slug, dupe name, dupe base name, and missing access cache
         foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names", "find_missing_access_caches") as $func) {
             foreach (self::$func() as $row) {
                 $total++;
             }
         }
         // one operation to rebuild path and url caches;
         $total += 1 * $item_count;
         $task->set("total", $total);
         $task->set("state", $state = self::FIX_STATE_START_MPTT);
         $task->set("ptr", 1);
         $task->set("completed", 0);
     }
     $completed = $task->get("completed");
     $state = $task->get("state");
     if (!module::get_var("gallery", "maintenance_mode")) {
         module::set_var("gallery", "maintenance_mode", 1);
     }
     // This is a state machine that checks each item in the database.  It verifies the following
     // attributes for an item.
     // 1. Left and right MPTT pointers are correct
     // 2. The .htaccess permission files for restricted items exist and are well formed.
     // 3. The relative_path_cache and relative_url_cache values are set to null.
     // 4. there are no album_cover_item_ids pointing to missing items
     //
     // We'll do a depth-first tree walk over our hierarchy using only the adjacency data because
     // we don't trust MPTT here (that might be what we're here to fix!).  Avoid avoid using ORM
     // calls as much as possible since they're expensive.
     //
     // NOTE: the MPTT check will only traverse items that have valid parents.  It's possible that
     // we have some tree corruption where there are items with parent ids to non-existent items.
     // We should probably do something about that.
     while ($state != self::FIX_STATE_DONE && microtime(true) - $start < 1.5) {
         switch ($state) {
             case self::FIX_STATE_START_MPTT:
                 $task->set("ptr", $ptr = 1);
                 $task->set("stack", item::root()->id . "L1");
                 $state = self::FIX_STATE_RUN_MPTT;
                 break;
             case self::FIX_STATE_RUN_MPTT:
                 $ptr = $task->get("ptr");
                 $stack = explode(" ", $task->get("stack"));
                 preg_match("/([0-9]+)([A-Z])([0-9]+)/", array_pop($stack), $matches);
                 // e.g. "12345L10"
                 list(, $id, $ptr_mode, $level) = $matches;
                 // Skip the 0th entry of matches.
                 switch ($ptr_mode) {
                     case "L":
                         // Albums could be parent nodes.
                         $stack[] = "{$id}R{$level}";
                         db::build()->update("items")->set("left_ptr", $ptr++)->where("id", "=", $id)->execute();
                         $level++;
                         foreach (db::build()->select(array("id", "type"))->from("items")->where("parent_id", "=", $id)->order_by("left_ptr", "DESC")->execute() as $child) {
                             $stack[] = $child->type == "album" ? "{$child->id}L{$level}" : "{$child->id}B{$level}";
                         }
                         $completed++;
                         break;
                     case "B":
                         // Non-albums must be leaf nodes.
                         db::build()->update("items")->set("left_ptr", $ptr++)->set("right_ptr", $ptr++)->set("level", $level)->set("relative_path_cache", null)->set("relative_url_cache", null)->where("id", "=", $id)->execute();
                         $completed += 2;
                         // we updated two pointers
                         break;
                     case "R":
                         db::build()->update("items")->set("right_ptr", $ptr++)->set("level", $level)->set("relative_path_cache", null)->set("relative_url_cache", null)->where("id", "=", $id)->execute();
                         $completed++;
                 }
                 $task->set("ptr", $ptr);
                 $task->set("stack", implode(" ", $stack));
                 if (empty($stack)) {
                     $state = self::FIX_STATE_START_DUPE_SLUGS;
                 }
                 break;
             case self::FIX_STATE_START_DUPE_SLUGS:
                 $stack = array();
                 foreach (self::find_dupe_slugs() as $row) {
                     list($parent_id, $slug) = explode(":", $row->parent_slug, 2);
                     $stack[] = join(":", array($parent_id, $slug));
                 }
                 if ($stack) {
                     $task->set("stack", implode(" ", $stack));
                     $state = self::FIX_STATE_RUN_DUPE_SLUGS;
                 } else {
                     $state = self::FIX_STATE_START_DUPE_NAMES;
                 }
                 break;
             case self::FIX_STATE_RUN_DUPE_SLUGS:
                 $stack = explode(" ", $task->get("stack"));
                 list($parent_id, $slug) = explode(":", array_pop($stack));
                 // We want to leave the first one alone and update all conflicts to be random values.
                 $fixed = 0;
                 $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("slug", "=", $slug)->find_all(1, 1);
                 if ($conflicts->count() && ($conflict = $conflicts->current())) {
                     $task->log("Fixing conflicting slug for item id {$conflict->id}");
                     db::build()->update("items")->set("slug", $slug . "-" . (string) rand(1000, 9999))->where("id", "=", $conflict->id)->execute();
                     // We fixed one conflict, but there might be more so put this parent back on the stack
                     // and try again.  We won't consider it completed when we don't fix a conflict.  This
                     // guarantees that we won't spend too long fixing one set of conflicts, and that we
                     // won't stop before all are fixed.
                     $stack[] = "{$parent_id}:{$slug}";
                     break;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $completed++;
                 if (empty($stack)) {
                     $state = self::FIX_STATE_START_DUPE_NAMES;
                 }
                 break;
             case self::FIX_STATE_START_DUPE_NAMES:
                 $stack = array();
                 foreach (self::find_dupe_names() as $row) {
                     list($parent_id, $name) = explode(":", $row->parent_name, 2);
                     $stack[] = join(":", array($parent_id, $name));
                 }
                 if ($stack) {
                     $task->set("stack", implode(" ", $stack));
                     $state = self::FIX_STATE_RUN_DUPE_NAMES;
                 } else {
                     $state = self::FIX_STATE_START_DUPE_BASE_NAMES;
                 }
                 break;
             case self::FIX_STATE_RUN_DUPE_NAMES:
                 // NOTE: This does *not* attempt to fix the file system!
                 $stack = explode(" ", $task->get("stack"));
                 list($parent_id, $name) = explode(":", array_pop($stack));
                 $fixed = 0;
                 // We want to leave the first one alone and update all conflicts to be random values.
                 $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("name", "=", $name)->find_all(1, 1);
                 if ($conflicts->count() && ($conflict = $conflicts->current())) {
                     $task->log("Fixing conflicting name for item id {$conflict->id}");
                     if (!$conflict->is_album() && preg_match("/^(.*)(\\.[^\\.\\/]*?)\$/", $conflict->name, $matches)) {
                         $item_base_name = $matches[1];
                         $item_extension = $matches[2];
                         // includes a leading dot
                     } else {
                         $item_base_name = $conflict->name;
                         $item_extension = "";
                     }
                     db::build()->update("items")->set("name", $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension)->where("id", "=", $conflict->id)->execute();
                     // We fixed one conflict, but there might be more so put this parent back on the stack
                     // and try again.  We won't consider it completed when we don't fix a conflict.  This
                     // guarantees that we won't spend too long fixing one set of conflicts, and that we
                     // won't stop before all are fixed.
                     $stack[] = "{$parent_id}:{$name}";
                     break;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $completed++;
                 if (empty($stack)) {
                     $state = self::FIX_STATE_START_DUPE_BASE_NAMES;
                 }
                 break;
             case self::FIX_STATE_START_DUPE_BASE_NAMES:
                 $stack = array();
                 foreach (self::find_dupe_base_names() as $row) {
                     list($parent_id, $base_name) = explode(":", $row->parent_base_name, 2);
                     $stack[] = join(":", array($parent_id, $base_name));
                 }
                 if ($stack) {
                     $task->set("stack", implode(" ", $stack));
                     $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES;
                 } else {
                     $state = self::FIX_STATE_START_ALBUMS;
                 }
                 break;
             case self::FIX_STATE_RUN_DUPE_BASE_NAMES:
                 // NOTE: This *does* attempt to fix the file system!  So, it must go *after* run_dupe_names.
                 $stack = explode(" ", $task->get("stack"));
                 list($parent_id, $base_name) = explode(":", array_pop($stack));
                 $base_name_escaped = Database::escape_for_like($base_name);
                 $fixed = 0;
                 // We want to leave the first one alone and update all conflicts to be random values.
                 $conflicts = ORM::factory("item")->where("parent_id", "=", $parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->where("type", "<>", "album")->find_all(1, 1);
                 if ($conflicts->count() && ($conflict = $conflicts->current())) {
                     $task->log("Fixing conflicting name for item id {$conflict->id}");
                     if (preg_match("/^(.*)(\\.[^\\.\\/]*?)\$/", $conflict->name, $matches)) {
                         $item_base_name = $matches[1];
                         // unlike $base_name, this always maintains capitalization
                         $item_extension = $matches[2];
                         // includes a leading dot
                     } else {
                         $item_base_name = $conflict->name;
                         $item_extension = "";
                     }
                     // Unlike conflicts found in run_dupe_names, these items are likely to have an intact
                     // file system.  Let's use the item save logic to rebuild the paths and rename the files
                     // if possible.
                     try {
                         $conflict->name = $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension;
                         $conflict->validate();
                         // If we get here, we're safe to proceed with save
                         $conflict->save();
                     } catch (Exception $e) {
                         // Didn't work.  Edit database directly without fixing file system.
                         db::build()->update("items")->set("name", $item_base_name . "-" . (string) rand(1000, 9999) . $item_extension)->where("id", "=", $conflict->id)->execute();
                     }
                     // We fixed one conflict, but there might be more so put this parent back on the stack
                     // and try again.  We won't consider it completed when we don't fix a conflict.  This
                     // guarantees that we won't spend too long fixing one set of conflicts, and that we
                     // won't stop before all are fixed.
                     $stack[] = "{$parent_id}:{$base_name}";
                     break;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $completed++;
                 if (empty($stack)) {
                     $state = self::FIX_STATE_START_ALBUMS;
                 }
                 break;
             case self::FIX_STATE_START_ALBUMS:
                 $stack = array();
                 foreach (db::build()->select("id")->from("items")->where("type", "=", "album")->execute() as $row) {
                     $stack[] = $row->id;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $state = self::FIX_STATE_RUN_ALBUMS;
                 break;
             case self::FIX_STATE_RUN_ALBUMS:
                 $stack = explode(" ", $task->get("stack"));
                 $id = array_pop($stack);
                 $item = ORM::factory("item", $id);
                 if ($item->album_cover_item_id) {
                     $album_cover_item = ORM::factory("item", $item->album_cover_item_id);
                     if (!$album_cover_item->loaded()) {
                         $item->album_cover_item_id = null;
                         $item->save();
                     }
                 }
                 $everybody = identity::everybody();
                 $view_col = "view_{$everybody->id}";
                 $view_full_col = "view_full_{$everybody->id}";
                 $intent = ORM::factory("access_intent")->where("item_id", "=", $id)->find();
                 if ($intent->{$view_col} === access::DENY) {
                     access::update_htaccess_files($item, $everybody, "view", access::DENY);
                 }
                 if ($intent->{$view_full_col} === access::DENY) {
                     access::update_htaccess_files($item, $everybody, "view_full", access::DENY);
                 }
                 $task->set("stack", implode(" ", $stack));
                 $completed++;
                 if (empty($stack)) {
                     $state = self::FIX_STATE_START_REBUILD_ITEM_CACHES;
                 }
                 break;
             case self::FIX_STATE_START_REBUILD_ITEM_CACHES:
                 $stack = array();
                 foreach (self::find_empty_item_caches(500) as $row) {
                     $stack[] = $row->id;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $state = self::FIX_STATE_RUN_REBUILD_ITEM_CACHES;
                 break;
             case self::FIX_STATE_RUN_REBUILD_ITEM_CACHES:
                 $stack = explode(" ", $task->get("stack"));
                 if (!empty($stack)) {
                     $id = array_pop($stack);
                     $item = ORM::factory("item", $id);
                     $item->relative_path();
                     // this rebuilds the cache and saves the item as a side-effect
                     $task->set("stack", implode(" ", $stack));
                     $completed++;
                 }
                 if (empty($stack)) {
                     // Try refilling the stack
                     foreach (self::find_empty_item_caches(500) as $row) {
                         $stack[] = $row->id;
                     }
                     $task->set("stack", implode(" ", $stack));
                     if (empty($stack)) {
                         $state = self::FIX_STATE_START_MISSING_ACCESS_CACHES;
                     }
                 }
                 break;
             case self::FIX_STATE_START_MISSING_ACCESS_CACHES:
                 $stack = array();
                 foreach (self::find_missing_access_caches_limited(500) as $row) {
                     $stack[] = $row->id;
                 }
                 $task->set("stack", implode(" ", $stack));
                 $state = self::FIX_STATE_RUN_MISSING_ACCESS_CACHES;
                 break;
             case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES:
                 $stack = array_filter(explode(" ", $task->get("stack")));
                 // filter removes empty/zero ids
                 if (!empty($stack)) {
                     $id = array_pop($stack);
                     $access_cache = ORM::factory("access_cache");
                     $access_cache->item_id = $id;
                     $access_cache->save();
                     $task->set("stack", implode(" ", $stack));
                     $completed++;
                 }
                 if (empty($stack)) {
                     // Try refilling the stack
                     foreach (self::find_missing_access_caches_limited(500) as $row) {
                         $stack[] = $row->id;
                     }
                     $task->set("stack", implode(" ", $stack));
                     if (empty($stack)) {
                         // The new cache rows are there, but they're incorrectly populated so we have to fix
                         // them.  If this turns out to be too slow, we'll have to refactor
                         // access::recalculate_permissions to allow us to do it in slices.
                         access::recalculate_album_permissions(item::root());
                         $state = self::FIX_STATE_DONE;
                     }
                 }
                 break;
         }
     }
     $task->set("state", $state);
     $task->set("completed", $completed);
     if ($state == self::FIX_STATE_DONE) {
         $task->done = true;
         $task->state = "success";
         $task->percent_complete = 100;
         module::set_var("gallery", "maintenance_mode", 0);
     } else {
         $task->percent_complete = round(100 * $completed / $total);
     }
     $task->status = t2("One operation complete", "%count / %total operations complete", $completed, array("total" => $total));
 }