public function verifyAndUpdatePlugins() { $plugins = Plugin::where('active', '=', 1)->get(); // Going to compare checksums // for each of these plugins $n = 0; foreach ($plugins as $num => $plugin) { $n++; // Defaults not to update $update = false; // fetching via http $xml = @file_get_contents($plugin->xml_url); if (!$xml) { echo 'Plugin (' . $n . '/' . sizeof($plugins) . "): \"" . $plugin->name . "\" Cannot get XML file via HTTP, Skipping.\n"; continue; } $crc = md5($xml); // compute crc if ($plugin->xml_crc != $crc || $plugin->name == NULL) { $update = true; // if we got // missing name or changing // crc, then we're going to // update that one } else { echo 'Plugin (' . $n . '/' . sizeof($plugins) . "): \"" . $plugin->name . "\" Already updated, Skipping.\n"; continue; } // loading XML OO-style with simplemxl $xml = new ValidableXMLPluginDescription($xml); if (!$xml->isValid()) { echo 'Plugin (' . $n . '/' . sizeof($plugins) . "): \"" . $plugin->name . "\" Unreadable/Non validable XML, Skipping.\n"; echo "Errors: \n"; foreach ($xml->errors as $error) { echo " - " . $error . "\n"; } continue; } $xml = $xml->contents; echo 'Plugin (' . $n . '/' . sizeof($plugins) . '): Updating ... '; $this->updatePlugin($plugin, $xml, $crc); } }
/** * Download * * This REST module hooks on * following URLs * * /download/:plugin_id */ use API\Core\Tool; use API\Model\Plugin; use API\Model\PluginDownload; use Illuminate\Database\Capsule\Manager as DB; use API\OAuthServer\OAuthHelper; $download = Tool::makeEndpoint(function ($key) use($app) { $plugin = Plugin::where('key', '=', $key)->first(); $plugin->download_count = DB::raw('download_count + 1'); $plugin->save(); $plugin_download = new PluginDownload(); $plugin_download->downloaded_at = DB::raw('NOW()'); $plugin_download->plugin_id = $plugin->id; $plugin_download->save(); /** * @MonkeyPatch * @todo remove this as soon as possible once * all our famous, star, since-day-one * contributors took the time * to update their XML file. */ $indepnetFixSearchPattern = '/https:\\/\\/forge\\.indepnet\\.net/'; if (preg_match($indepnetFixSearchPattern, $plugin->download_url)) {
/** * Task : updatePlugin() * * Note: This function does direct output, * in fact it builds the log string * that concerns the update of a * plugin. */ private function updatePlugin($plugin, $index = null, $length = null, $subtasks) { // Displaying index / length $this->outputStr('Plugin (' . $index . '/' . $length . ') (id #' . $plugin->id . '): '); $update = false; // This can be used to detect the state // in some way (think about it) $this->currentXml = null; $this->currentPluginState = null; // fetching via http $unableToFetch = false; $httpClient = new GuzzleHttpClient(); try { $pluginXmlRequest = $httpClient->get($plugin->xml_url, ["headers" => ["User-Agent" => Tool::getConfig()['glpi_plugin_directory_user_agent']]]); } catch (\GuzzleHttp\Exception\ConnectException $e) { $unableToFetch = true; } finally { if ($unableToFetch || !$unableToFetch && $pluginXmlRequest->getStatusCode() != 200) { if ($this->pluginMaxConsecutiveXmlFetchFails) { $fetchFailCount = $plugin->incrementXmlFetchFailCount(); if ($fetchFailCount == $this->pluginMaxConsecutiveXmlFetchFails) { $this->triggerPluginXmlStateChange($plugin, 'bad_xml_url', true, in_array('alert_plugin_team_on_xml_state_change', $subtasks)); $plugin->resetXmlFetchFailCount(); } } else { $this->triggerPluginXmlStateChange($plugin, 'bad_xml_url', true, in_array('alert_plugin_team_on_xml_state_change', $subtasks)); } $this->outputStr($plugin->xml_url . "\" Cannot get XML file via HTTP, Skipping.\n"); if ($this->throwsExceptions) { throw new InvalidXML('url', $plugin->xml_url); } return false; } else { $plugin->resetXmlFetchFailCount(); } } $xml = $pluginXmlRequest->getBody(); $this->currentXml = (string) $xml; $crc = md5($xml); // compute crc if ($plugin->xml_crc != $crc || $plugin->name == NULL) { $update = true; // if we got // missing name or changing // crc, then we're going to // update that one. // missing name means it's // the first time the plugin // is updated } else { $this->outputStr("\"" . $plugin->name . "\" Already up-to-date, Skipping.\n"); $this->triggerPluginXmlStateChange($plugin, 'passing', true, in_array('alert_plugin_team_on_xml_state_change', $subtasks)); return false; } try { $xml = new ValidableXMLPluginDescription($xml); $xml->validate(); } catch (\API\Exception\InvalidXML $e) { $_unreadable = ''; if (isset($xml->contents) && $xml->contents->name && sizeof($xml->contents->name->children()) < 1 && strlen((string) $xml->contents->name) < 80) { $_unreadable .= '"' . (string) $xml->contents->key . '" '; } elseif ($plugin->name) { $_unreadable .= '"' . $plugin->name . '" '; } $this->triggerPluginXmlStateChange($plugin, 'xml_error', true, in_array('alert_plugin_team_on_xml_state_change', $subtasks)); $_unreadable .= "Unreadable/Non validable XML, error: " . $e->getRepresentation() . " Skipping.\n"; $this->outputStr($_unreadable); if ($this->throwsExceptions) { throw $e; } return false; } $xml = $xml->contents; if (!$plugin->name) { $this->outputStr("first time update, found name \"" . $xml->name . "\"..."); if (Plugin::where('name', '=', $xml->name)->first()) { $this->outputStr(" already exists. skipping."); // this would be amazing to alert the administrators // of that. new Mailer; ? return false; } $firstTimeUpdate = true; } else { if ($plugin->name != $xml->name) { $this->outputStr(" requested name change to \"" . $xml->name . "\" ..."); if (Plugin::where('name', '=', $xml->name)->first()) { $this->outputStr(" but name already exists. skipping."); // this would be amazing to alert the administrators // of that. new Mailer; ? return false; } } $firstTimeUpdate = false; $this->outputStr("\"" . $plugin->name . "\""); } $this->outputStr(" going to be synced with xml ..."); $this->triggerPluginXmlStateChange($plugin, 'passing', true, in_array('alert_plugin_team_on_xml_state_change', $subtasks)); // Updating basic infos $plugin->logo_url = $xml->logo; $plugin->name = $xml->name; $plugin->key = $xml->key; $plugin->homepage_url = $xml->homepage; $plugin->download_url = $xml->download; $plugin->issues_url = $xml->issues; $plugin->readme_url = $xml->readme; $plugin->license = $xml->license; // reading descriptions, // mapping type=>lang relation to lang=>type $descriptions = []; foreach ($xml->description->children() as $type => $descs) { if (in_array($type, ['short', 'long'])) { foreach ($descs->children() as $_lang => $content) { $descriptions[$_lang][$type] = (string) $content; } } } // Delete current descriptions $plugin->descriptions()->delete(); // Refreshing descriptions foreach ($descriptions as $lang => $_type) { $description = new PluginDescription(); $description->lang = $lang; foreach ($_type as $type => $html) { $description[$type . '_description'] = $html; } $description->plugin_id = $plugin->id; $description->save(); } // Refreshing authors $plugin->authors()->detach(); $clean_authors = []; foreach ($xml->authors->children() as $author) { $_clean_authors = Author::fixKnownDuplicates((string) $author); foreach ($_clean_authors as $author) { $clean_authors[] = $author; } } foreach ($clean_authors as $_author) { $found = Author::where('name', '=', $_author)->first(); if (sizeof($found) < 1) { $author = new Author(); $author->name = $_author; $author->save(); } else { $author = $found; } if (!$plugin->authors->find($author->id)) { $plugin->authors()->attach($author); } } // Refreshing versions $plugin->versions()->delete(); foreach ($xml->versions->children() as $_version) { foreach ($_version->compatibility as $compat) { $version = new PluginVersion(); $version->num = trim((string) $_version->num); $version->compatibility = trim((string) $compat); $version->plugin_id = $plugin->id; $version->save(); } } // Refreshing screenshots if (isset($xml->screenshots)) { $plugin->screenshots()->delete(); foreach ($xml->screenshots->children() as $url) { $screenshot = new PluginScreenshot(); $screenshot->url = (string) $url; $screenshot->plugin_id = $plugin->id; $screenshot->save(); } } // Reassociating plugin to tags $plugin->tags()->detach(); foreach ($xml->tags->children() as $lang => $tags) { foreach ($tags->children() as $_tag) { $found = Tag::where('tag', '=', (string) $_tag)->where('lang', '=', $lang)->first(); if (sizeof($found) < 1) { $tag = new Tag(); $tag->tag = (string) $_tag; $tag->lang = $lang; $tag->key = Tool::getUrlSlug((string) $_tag); $tag->save(); } else { $tag = $found; } $tag->plugins()->attach($plugin); } } // Reassociating plugin to langs $plugin->langs()->detach(); foreach ($xml->langs->children() as $lang) { $lang = (string) $lang; $_lang = PluginLang::where('lang', '=', $lang)->first(); if (!$_lang) { $_lang = new PluginLang(); $_lang->lang = $lang; $_lang->save(); } $_lang->plugins()->attach($plugin); } // new crc $plugin->xml_crc = $crc; // new updated timestamp $plugin->date_updated = \Illuminate\Database\Capsule\Manager::raw('NOW()'); $plugin->save(); $this->outputStr(" OK."); if (in_array('alert_watchers', $subtasks)) { $this->alertWatchers($plugin); $this->outputStr("\n"); } else { $this->outputStr("\n"); } }
} } // Quickly validating if (Plugin::where('xml_url', '=', $body->plugin_url)->count() > 0) { return Tool::endWithJson(["error" => "That plugin XML URL has already been submitted."]); } $xml = @file_get_contents($body->plugin_url); if (!$xml) { return Tool::endWithJson(["error" => "We cannot fetch that URL."]); } $xml = new ValidableXMLPluginDescription($xml); if (!$xml->isValid()) { return Tool::endWithJson(["error" => "Unreadable/Non validable XML.", "details" => $xml->errors]); } $xml = $xml->contents; if (Plugin::where('key', '=', $xml->key)->count() > 0) { return Tool::endWithJson(["error" => "Your XML describe a plugin whose key already exists in our database."]); } $plugin = new Plugin(); $plugin->xml_url = $body->plugin_url; $plugin->date_added = DB::raw('NOW()'); $plugin->active = false; $plugin->save(); $msg_alerts_settings = Tool::getConfig()['msg_alerts']; $recipients = ''; $i = 0; foreach ($msg_alerts_settings['recipients'] as $recipient) { if ($i > 0) { $recipients .= ', '; } $recipients .= $recipient;