/** * Method used to end with * a Rss value from * an endpoint. */ public static function endWithRSS($_payload, $feed_title = '', $code = 200) { global $app; $plugins = self::getPayload($_payload); // retrieve app config $app_config = self::getConfig(); $url = $app_config['client_url']; // create a feed $feed = new \Suin\RSSWriter\Feed(); // create a channel $channel = new \Suin\RSSWriter\Channel(); $channel->title($feed_title)->url($url)->language('en-US')->pubDate(time())->lastBuildDate(time())->ttl(60)->appendTo($feed); foreach ($plugins->toArray() as $current_plugin) { $plugin = Plugin::with('descriptions', 'authors', 'versions', 'screenshots', 'tags', 'langs')->short()->where('key', '=', $current_plugin['key'])->where('active', '=', 1)->first(); if ($plugin) { $plugin = $plugin->toArray(); // compute date $date = $plugin['date_updated'] != "" ? $plugin['date_updated'] : ($plugin['date_added'] != "" ? $plugin['date_added'] : date("Y-m-d")); // compute description $description = ""; foreach ($plugin['descriptions'] as $current_desc) { if ($current_desc['lang'] == 'en') { $description = $current_desc['long_description']; break; } } if (empty($description)) { $description = $plugin['descriptions'][0]['long_description']; } // find last version (the first in list) $version_num = $version_compat = ""; $last_version = array_shift($plugin['versions']); if ($last_version != NULL) { $version_num = $last_version['num']; $version_compat = $last_version['compatibility']; $description .= "<br />\n <br /> Version: {$version_num}\n <br /> Compatibility: {$version_compat}"; } // add plugin to feed $item = new \Suin\RSSWriter\Item(); $item->title($plugin['name'] . " " . $version_num)->description($description)->contentEncoded($description)->url($url . '/#/plugin/' . $plugin['key'])->pubDate(strtotime($date))->guid($plugin['name'] . "_" . $date . "_" . $version_num, true)->appendTo($channel); } } // render feed $app->halt($code, $feed->render()); }
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); } }
<?php use Illuminate\Database\Capsule\Manager as DB; use API\Core\Tool; use API\Model\Plugin; use API\OAuthServer\OAuthHelper; $version_plugins = Tool::makeEndpoint(function ($version) { OAuthHelper::needsScopes(['version', 'plugins']); $plugins = Tool::paginateCollection(Plugin::short()->with('authors', 'versions', 'descriptions')->withAverageNote()->descWithLang(Tool::getRequestLang())->withGlpiVersion($version)); Tool::endWithJson($plugins); }); $app->get('/version/:version/plugin', $version_plugins); $app->options('/version/:version/plugin', function () { });
}); $tag_single = Tool::makeEndpoint(function ($key) use($app) { OAuthHelper::needsScopes(['tag']); $tag = Tag::where('key', '=', $key)->first(); if ($tag == NULL) { throw new \API\Exception\ResourceNotFound('Tag', $key); } Tool::endWithJson($tag); }); $tag_plugins = Tool::makeEndpoint(function ($key) use($app) { OAuthHelper::needsScopes(['tag', 'plugins']); $tag = Tag::where('key', '=', $key)->first(); if ($tag == NULL) { throw new \API\Exception\ResourceNotFound('Tag', $key); } $plugins = Tool::paginateCollection(Plugin::with('versions', 'authors')->short()->withAverageNote()->descWithLang(Tool::getRequestLang())->withTag($tag)); Tool::endWithJson($plugins); }); // HTTP rest map $app->get('/tags', $tags_all); $app->get('/tags/top', $tags_top); $app->get('/tags/:id/plugin', $tag_plugins); $app->get('/tags/:id', $tag_single); $app->options('/tags', function () { }); $app->options('/tags/top', function () { }); $app->options('/tags/:id/plugin', function ($id) { }); $app->options('/tags/:id', function ($id) { });
/** * 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"); } }
$all = Tool::paginateCollection(\API\Model\Author::mostActive()->contributorsOnly()); Tool::endWithJson($all); }; $top = function () use($app) { $top = \API\Model\Author::mostActive(10)->get(); Tool::endWithJson($top); }; $single = function ($id) use($app) { $single = \API\Model\Author::withPluginCount()->find($id); Tool::endWithJson($single); }; $author_plugins = function ($id) use($app) { $author_plugins = \API\Model\Author::find($id); if (!$author_plugins) { return Tool::endWithJson(["error" => "Cannot find author"]); } Tool::endWithJson(Tool::paginateCollection(\API\Model\Plugin::with('versions', 'authors')->short()->withDownloads()->withAverageNote()->descWithLang(Tool::getRequestLang())->whereAuthor($author_plugins->id))); }; // HTTP REST Map $app->get('/author', $all); $app->get('/author/top', $top); $app->get('/author/:id', $single); $app->get('/author/:id/plugin', $author_plugins); $app->options('/author', function () { }); $app->options('/author/top', function () { }); $app->options('/author/:id', function ($id) { }); $app->options('/author/:id/plugin', function ($id) { });
<?php /** * Search * * This REST module hooks on * following URLs * * /search */ use API\Core\Tool; use Illuminate\Database\Capsule\Manager as DB; // Minimal length of search string $search_min_length = 2; $search = function () use($app) { global $search_min_length, $allowed_languages; $body = Tool::getBody(); if ($body == NULL || !isset($body->query_string) || strlen($body->query_string) < $search_min_length) { return Tool::endWithJson(["error" => "Your search string needs to " . "have at least " . $search_min_length . " chars"], 400); } $query_string = $body->query_string; $_search = Tool::paginateCollection(\API\Model\Plugin::short()->with('authors', 'versions', 'descriptions')->withDownloads()->withAverageNote()->descWithLang(Tool::getRequestLang())->where('name', 'LIKE', "%{$query_string}%")->orWhere('plugin_description.short_description', 'LIKE', "%{$query_string}%")->orWhere('plugin_description.long_description', 'LIKE', "%{$query_string}%")); Tool::endWithJson($_search); }; $app->post('/search', $search); $app->options('/search', function () { });
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; $i++; } mail($recipients, $msg_alerts_settings['subject_prefix'] . '[PLUGIN SUBMISSION] ' . $xml->name . ' (' . $xml->key . ')', 'A new plugin "' . $xml->name . '" with key "' . $xml->key . '" has been submitted and is awaiting to be verified. It has db id #' . $plugin->id, "From: GLPI Plugins <*****@*****.**>");
<?php /** * Search * * This REST module hooks on * following URLs * * /search */ use API\Core\Tool; use Illuminate\Database\Capsule\Manager as DB; use API\OAuthServer\OAuthHelper; // Minimal length of search string $search_min_length = 2; $search = Tool::makeEndpoint(function () use($app) { OAuthHelper::needsScopes(['plugins:search']); global $search_min_length, $allowed_languages; $body = Tool::getBody(); if ($body == NULL || !isset($body->query_string) || strlen($body->query_string) < $search_min_length) { Tool::endWithJson(["error" => "Your search string needs to " . "have at least " . $search_min_length . " chars"], 400); } $query_string = $body->query_string; $_search = Tool::paginateCollection(\API\Model\Plugin::short()->with('authors', 'versions', 'descriptions')->withAverageNote()->descWithLang(Tool::getRequestLang())->where('active', '=', true)->where(function ($q) use($query_string) { return $q->where('name', 'LIKE', "%{$query_string}%")->orWhere('key', 'LIKE', "%{$query_string}%")->orWhere('plugin_description.short_description', 'LIKE', "%{$query_string}%")->orWhere('plugin_description.long_description', 'LIKE', "%{$query_string}%"); })->orderBy('download_count', 'DESC')->orderBy('note', 'DESC')->orderBy('name', 'ASC')); Tool::endWithJson($_search); }); $app->post('/search', $search); $app->options('/search', function () { });
} // Quickly validating if (Plugin::where('xml_url', '=', $body->plugin_url)->count() > 0) { throw new UnavailableName('XML_URL', $body->plugin_url); } $xml = @file_get_contents($body->plugin_url); if (!$xml) { throw new InvalidXML('url', $body->plugin_url); } $xml = new ValidableXMLPluginDescription($xml); $xml->validate(); $xml = $xml->contents; if (Plugin::where('key', '=', $xml->key)->count() > 0) { throw new UnavailableName('Plugin', $xml->key); } $plugin = new Plugin(); $plugin->xml_url = $body->plugin_url; $plugin->date_added = DB::raw('NOW()'); $plugin->active = false; $plugin->download_count = 0; $plugin->save(); $plugin->permissions()->attach($user); $user = $plugin->permissions()->where('user_id', '=', $user->id)->first(); $user->pivot['admin'] = true; $user->pivot->save(); $mailer = new Mailer(); $mailer->sendMail('plugin_submission.html', Tool::getConfig()['msg_alerts']['local_admins'], '[PLUGIN SUBMISSION] ' . $xml->name . ' (' . $xml->key . ')', ['plugin_xml' => (array) $xml]); Tool::endWithJson(["success" => true]); }); // HTTP REST Map $app->get('/plugin', $all);