|
|
@@ -1,6 +1,14 @@
|
|
|
<?php
|
|
|
|
|
|
+/**
|
|
|
+ * Controller to handle every import and export actions.
|
|
|
+ */
|
|
|
class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
+ /**
|
|
|
+ * This action is called before every other action in that class. It is
|
|
|
+ * the common boiler plate for every action. It is triggered by the
|
|
|
+ * underlying framework.
|
|
|
+ */
|
|
|
public function firstAction() {
|
|
|
if (!$this->view->loginOk) {
|
|
|
Minz_Error::error(
|
|
|
@@ -16,13 +24,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$this->feedDAO = FreshRSS_Factory::createFeedDao();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This action displays the main page for import / export system.
|
|
|
+ */
|
|
|
public function indexAction() {
|
|
|
- $this->view->categories = $this->catDAO->listCategories();
|
|
|
$this->view->feeds = $this->feedDAO->listFeeds();
|
|
|
-
|
|
|
Minz_View::prependTitle(_t('import_export') . ' · ');
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This action handles import action.
|
|
|
+ *
|
|
|
+ * It must be reached by a POST request.
|
|
|
+ *
|
|
|
+ * Parameter is:
|
|
|
+ * - file (default: nothing!)
|
|
|
+ * Available file types are: zip, json or xml.
|
|
|
+ */
|
|
|
public function importAction() {
|
|
|
if (!Minz_Request::isPost()) {
|
|
|
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
|
|
|
@@ -92,10 +110,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$error = $this->importOpml($opml_file);
|
|
|
}
|
|
|
foreach ($list_files['json_starred'] as $article_file) {
|
|
|
- $error = $this->importArticles($article_file, true);
|
|
|
+ $error = $this->importJson($article_file, true);
|
|
|
}
|
|
|
foreach ($list_files['json_feed'] as $article_file) {
|
|
|
- $error = $this->importArticles($article_file);
|
|
|
+ $error = $this->importJson($article_file);
|
|
|
}
|
|
|
|
|
|
// And finally, we get import status and redirect to the home page
|
|
|
@@ -105,11 +123,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
Minz_Request::good($content_notif);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method tries to guess the file type based on its name.
|
|
|
+ *
|
|
|
+ * Itis a *very* basic guess file type function. Only based on filename.
|
|
|
+ * That's could be improved but should be enough for what we have to do.
|
|
|
+ *
|
|
|
+ * @todo move into lib_rss.php
|
|
|
+ */
|
|
|
private function guessFileType($filename) {
|
|
|
- // A *very* basic guess file type function. Only based on filename
|
|
|
- // That's could be improved but should be enough, at least for a first
|
|
|
- // implementation.
|
|
|
-
|
|
|
if (substr_compare($filename, '.zip', -4) === 0) {
|
|
|
return 'zip';
|
|
|
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
|
|
|
@@ -125,6 +147,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method parses and imports an OPML file.
|
|
|
+ *
|
|
|
+ * @param string $opml_file the OPML file content.
|
|
|
+ * @return boolean true if an error occured, false else.
|
|
|
+ */
|
|
|
private function importOpml($opml_file) {
|
|
|
$opml_array = array();
|
|
|
try {
|
|
|
@@ -139,13 +167,22 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $this->addOpmlElements($opml_array['body']);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method imports an OPML file based on its body.
|
|
|
+ *
|
|
|
+ * @param array $opml_elements an OPML element (body or outline).
|
|
|
+ * @param string $parent_cat the name of the parent category.
|
|
|
+ * @return boolean true if an error occured, false else.
|
|
|
+ */
|
|
|
private function addOpmlElements($opml_elements, $parent_cat = null) {
|
|
|
$error = false;
|
|
|
foreach ($opml_elements as $elt) {
|
|
|
$res = false;
|
|
|
if (isset($elt['xmlUrl'])) {
|
|
|
+ // If xmlUrl exists, it means it is a feed
|
|
|
$res = $this->addFeedOpml($elt, $parent_cat);
|
|
|
} else {
|
|
|
+ // No xmlUrl? It should be a category!
|
|
|
$res = $this->addCategoryOpml($elt, $parent_cat);
|
|
|
}
|
|
|
|
|
|
@@ -158,6 +195,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $error;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method imports an OPML feed element.
|
|
|
+ *
|
|
|
+ * @param array $feed_elt an OPML element (must be a feed element).
|
|
|
+ * @param string $parent_cat the name of the parent category.
|
|
|
+ * @return boolean true if an error occured, false else.
|
|
|
+ */
|
|
|
private function addFeedOpml($feed_elt, $parent_cat) {
|
|
|
if (is_null($parent_cat)) {
|
|
|
// This feed has no parent category so we get the default one
|
|
|
@@ -165,8 +209,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
}
|
|
|
|
|
|
$cat = $this->catDAO->searchByName($parent_cat);
|
|
|
-
|
|
|
if (!$cat) {
|
|
|
+ // If there is not $cat, it means parent category does not exist in
|
|
|
+ // database. It should not happened!
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -203,6 +248,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $error;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method imports an OPML category element.
|
|
|
+ *
|
|
|
+ * @param array $cat_elt an OPML element (must be a category element).
|
|
|
+ * @param string $parent_cat the name of the parent category.
|
|
|
+ * @return boolean true if an error occured, false else.
|
|
|
+ */
|
|
|
private function addCategoryOpml($cat_elt, $parent_cat) {
|
|
|
// Create a new Category object
|
|
|
$cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text']));
|
|
|
@@ -223,7 +275,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $error;
|
|
|
}
|
|
|
|
|
|
- private function importArticles($article_file, $starred = false) {
|
|
|
+ /**
|
|
|
+ * This method import a JSON-based file (Google Reader format).
|
|
|
+ *
|
|
|
+ * @param string $article_file the JSON file content.
|
|
|
+ * @param boolean $starred true if articles from the file must be starred.
|
|
|
+ * @return boolean true if an error occured, false else.
|
|
|
+ */
|
|
|
+ private function importJson($article_file, $starred = false) {
|
|
|
$article_object = json_decode($article_file, true);
|
|
|
if (is_null($article_object)) {
|
|
|
Minz_Log::warning('Try to import a non-JSON file');
|
|
|
@@ -232,16 +291,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
|
|
|
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
|
|
|
|
|
|
- $google_compliant = (
|
|
|
- strpos($article_object['id'], 'com.google') !== false
|
|
|
- );
|
|
|
+ $google_compliant = strpos($article_object['id'], 'com.google') !== false;
|
|
|
|
|
|
$error = false;
|
|
|
$article_to_feed = array();
|
|
|
|
|
|
// First, we check feeds of articles are in DB (and add them if needed).
|
|
|
foreach ($article_object['items'] as $item) {
|
|
|
- $feed = $this->addFeedArticles($item['origin'], $google_compliant);
|
|
|
+ $feed = $this->addFeedJson($item['origin'], $google_compliant);
|
|
|
if (is_null($feed)) {
|
|
|
$error = true;
|
|
|
} else {
|
|
|
@@ -254,6 +311,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$this->entryDAO->beginTransaction();
|
|
|
foreach ($article_object['items'] as $item) {
|
|
|
if (!isset($article_to_feed[$item['id']])) {
|
|
|
+ // Related feed does not exist for this entry, do nothing.
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
@@ -263,6 +321,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
'summary' : 'content';
|
|
|
$tags = $item['categories'];
|
|
|
if ($google_compliant) {
|
|
|
+ // Remove tags containing "/state/com.google" which are useless.
|
|
|
$tags = array_filter($tags, function($var) {
|
|
|
return strpos($var, '/state/com.google') === false;
|
|
|
});
|
|
|
@@ -288,7 +347,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $error;
|
|
|
}
|
|
|
|
|
|
- private function addFeedArticles($origin, $google_compliant) {
|
|
|
+ /**
|
|
|
+ * This method import a JSON-based feed (Google Reader format).
|
|
|
+ *
|
|
|
+ * @param array $origin represents a feed.
|
|
|
+ * @param boolean $google_compliant takes care of some specific values if true.
|
|
|
+ * @return FreshRSS_Feed if feed is in database at the end of the process,
|
|
|
+ * else null.
|
|
|
+ */
|
|
|
+ private function addFeedJson($origin, $google_compliant) {
|
|
|
$default_cat = $this->catDAO->getDefault();
|
|
|
|
|
|
$return = null;
|
|
|
@@ -298,14 +365,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$website = $origin['htmlUrl'];
|
|
|
|
|
|
try {
|
|
|
- // Create a Feed object and add it in DB
|
|
|
+ // Create a Feed object and add it in database.
|
|
|
$feed = new FreshRSS_Feed($url);
|
|
|
$feed->_category($default_cat->id());
|
|
|
$feed->_name($name);
|
|
|
$feed->_website($website);
|
|
|
|
|
|
// addFeedObject checks if feed is already in DB so nothing else to
|
|
|
- // check here
|
|
|
+ // check here.
|
|
|
$id = $this->feedDAO->addFeedObject($feed);
|
|
|
|
|
|
if ($id !== false) {
|
|
|
@@ -319,6 +386,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $return;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This action handles export action.
|
|
|
+ *
|
|
|
+ * This action must be reached by a POST request.
|
|
|
+ *
|
|
|
+ * Parameters are:
|
|
|
+ * - export_opml (default: false)
|
|
|
+ * - export_starred (default: false)
|
|
|
+ * - export_feeds (default: array()) a list of feed ids
|
|
|
+ */
|
|
|
public function exportAction() {
|
|
|
if (!Minz_Request::isPost()) {
|
|
|
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
|
|
|
@@ -336,7 +413,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
}
|
|
|
|
|
|
if ($export_starred) {
|
|
|
- $export_files['starred.json'] = $this->generateArticles('starred');
|
|
|
+ $export_files['starred.json'] = $this->generateEntries('starred');
|
|
|
}
|
|
|
|
|
|
foreach ($export_feeds as $feed_id) {
|
|
|
@@ -344,9 +421,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
if ($feed) {
|
|
|
$filename = 'feed_' . $feed->category() . '_'
|
|
|
. $feed->id() . '.json';
|
|
|
- $export_files[$filename] = $this->generateArticles(
|
|
|
- 'feed', $feed
|
|
|
- );
|
|
|
+ $export_files[$filename] = $this->generateEntries('feed', $feed);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -366,10 +441,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$type = $this->guessFileType($filename);
|
|
|
$this->exportFile('freshrss_' . $filename, $export_files[$filename], $type);
|
|
|
} else {
|
|
|
+ // Nothing to do...
|
|
|
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method returns the OPML file based on user subscriptions.
|
|
|
+ *
|
|
|
+ * @return string the OPML file content.
|
|
|
+ */
|
|
|
private function generateOpml() {
|
|
|
$list = array();
|
|
|
foreach ($this->catDAO->listCategories() as $key => $cat) {
|
|
|
@@ -381,7 +462,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $this->view->helperToString('export/opml');
|
|
|
}
|
|
|
|
|
|
- private function generateArticles($type, $feed = NULL) {
|
|
|
+ /**
|
|
|
+ * This method returns a JSON file content.
|
|
|
+ *
|
|
|
+ * @param string $type must be "starred" or "feed"
|
|
|
+ * @param FreshRSS_Feed $feed feed of which we want to get entries.
|
|
|
+ * @return string the JSON file content.
|
|
|
+ */
|
|
|
+ private function generateEntries($type, $feed = NULL) {
|
|
|
$this->view->categories = $this->catDAO->listCategories();
|
|
|
|
|
|
if ($type == 'starred') {
|
|
|
@@ -389,8 +477,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
$this->view->type = 'starred';
|
|
|
$unread_fav = $this->entryDAO->countUnreadReadFavorites();
|
|
|
$this->view->entries = $this->entryDAO->listWhere(
|
|
|
- 's', '', FreshRSS_Entry::STATE_ALL, 'ASC',
|
|
|
- $unread_fav['all']
|
|
|
+ 's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all']
|
|
|
);
|
|
|
} elseif ($type == 'feed' && !is_null($feed)) {
|
|
|
$this->view->list_title = _t('feed_list', $feed->name());
|
|
|
@@ -405,6 +492,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
return $this->view->helperToString('export/articles');
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method zips a list of files and returns it by HTTP.
|
|
|
+ *
|
|
|
+ * @param array $files list of files where key is filename and value the content.
|
|
|
+ * @throws Exception if Zip extension is not loaded.
|
|
|
+ */
|
|
|
private function exportZip($files) {
|
|
|
if (!extension_loaded('zip')) {
|
|
|
throw new Exception();
|
|
|
@@ -428,6 +521,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
|
|
|
unlink($zip_file);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * This method returns a single file (OPML or JSON) by HTTP.
|
|
|
+ *
|
|
|
+ * @param string $filename
|
|
|
+ * @param string $content
|
|
|
+ * @param string $type the file type (opml, json_feed or json_starred).
|
|
|
+ * If equals to unknown, nothing happens.
|
|
|
+ */
|
|
|
private function exportFile($filename, $content, $type) {
|
|
|
if ($type === 'unknown') {
|
|
|
return;
|