' . __('Dotclear cache is not writable or not well configured!') . '
';
-}
-
-echo '
-';
-
-if ($s->pub_active) {
- echo sprintf(
- '
%s
',
- $pub_page_url,
- $pub_page_url,
- __('View the public list of feeds')
- );
-}
-
-echo '
-
' . __('Rules') . '
-
-
-
-
' .
-form::combo('post_status_new', $combo_status, $post_status_new) . '
-
-
' .
-form::combo('feeduser', $combo_admins, $feeduser) . '
-
-
' .
-form::combo('tag_case', $combo_tagcase, $tag_case) . '
-
-
-
-
' .
-form::combo('bhv_pub_upd', $combo_pubupd, $bhv_pub_upd) . '
-
-
-
-
-
-
-
-
-
-
' . __('Redirect to original post on:') . '
';
-
-foreach ($zc->getPublicUrlTypes() as $k => $v) {
- echo sprintf(
- '',
- $v,
- form::checkbox(['post_title_redir[]', 'post_title_redir_' . $v], $v, in_array($v, $post_title_redir)),
- __($k)
- );
-}
-echo '
-
-
-
-
-
' . __('Show full content on:') . '
';
-
-foreach ($zc->getPublicUrlTypes() as $k => $v) {
- echo sprintf(
- '',
- $v,
- form::checkbox(['post_full_tpl[]', 'post_full_tpl_' . $v], $v, in_array($v, $post_full_tpl)),
- __($k)
- );
-}
-
-echo '
';
-
-dcPage::helpBlock('zoneclearFeedServer');
diff --git a/src/FeedRow.php b/src/FeedRow.php
new file mode 100644
index 0000000..7827971
--- /dev/null
+++ b/src/FeedRow.php
@@ -0,0 +1,72 @@
+id = is_numeric($this->rs->f('feed_id')) ? (int) $this->rs->f('feed_id') : 0;
+ $this->creadt = is_numeric($this->rs->f('feed_creadt')) ? (int) $this->rs->f('feed_creadt') : 0;
+ $this->upddt = is_numeric($this->rs->f('feed_upddt')) ? (int) $this->rs->f('feed_upddt') : 0;
+ $this->type = is_string($this->rs->f('feed_type')) ? $this->rs->f('feed_type') : '';
+ $this->blog_id = is_string($this->rs->f('blog_id')) ? $this->rs->f('blog_id') : null;
+ $this->cat_id = is_numeric($this->rs->f('cat_id')) ? (int) $this->rs->f('cat_id') : null;
+ $this->upd_int = is_numeric($this->rs->f('feed_upd_int')) ? (int) $this->rs->f('feed_upd_int') : 0;
+ $this->upd_last = is_numeric($this->rs->f('feed_upd_last')) ? (int) $this->rs->f('feed_upd_last') : 0;
+ $this->status = is_numeric($this->rs->f('feed_status')) ? (int) $this->rs->f('feed_status') : 0;
+ $this->name = is_string($this->rs->f('feed_name')) ? $this->rs->f('feed_name') : '';
+ $this->desc = is_string($this->rs->f('feed_desc')) ? $this->rs->f('feed_desc') : '';
+ $this->url = is_string($this->rs->f('feed_url')) ? $this->rs->f('feed_url') : '';
+ $this->feed = is_string($this->rs->f('feed_feed')) ? $this->rs->f('feed_feed') : '';
+ $this->tags = is_string($this->rs->f('feed_tags')) ? $this->rs->f('feed_tags') : '';
+ $this->get_tags = !empty($this->rs->f('feed_get_tags'));
+ $this->owner = is_string($this->rs->f('feed_owner')) ? $this->rs->f('feed_owner') : '';
+ $this->tweeter = is_string($this->rs->f('feed_tweeter')) ? $this->rs->f('feed_tweeter') : '';
+ $this->lang = is_string($this->rs->f('feed_lang')) ? $this->rs->f('feed_lang') : '';
+ $this->nb_out = is_numeric($this->rs->f('feed_nb_out')) ? (int) $this->rs->f('feed_nb_out') : 0;
+ $this->nb_in = is_numeric($this->rs->f('feed_nb_in')) ? (int) $this->rs->f('feed_nb_in') : 0;
+ }
+}
diff --git a/src/FeedsActions.php b/src/FeedsActions.php
index f9bccb8..35412f2 100644
--- a/src/FeedsActions.php
+++ b/src/FeedsActions.php
@@ -10,19 +10,35 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
-class zcfsFeedsActions extends dcActions
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use dcActions;
+use dcCore;
+use dcPage;
+use Dotclear\Database\MetaRecord;
+use Dotclear\Helper\Html\Html;
+use Exception;
+
+/**
+ * Backend feeds list actions handler.
+ */
+class FeedsActions extends dcActions
{
- public $zcfs;
+ public ZoneclearFeedServer $zcfs;
- public function __construct($uri, $redirect_args = [])
+ /**
+ * @param string $uri
+ * @param array $redirect_args
+ */
+ public function __construct(string $uri, array $redirect_args = [])
{
- $this->zcfs = new zoneclearFeedServer();
+ $this->zcfs = ZoneclearFeedServer::instance();
parent::__construct($uri, $redirect_args);
+
$this->redirect_fields = [
'sortby', 'order', 'page', 'nb',
];
@@ -31,13 +47,15 @@ class zcfsFeedsActions extends dcActions
$this->loadDefaults();
}
- protected function loadDefaults()
+ protected function loadDefaults(): void
{
- zcfsDefaultFeedsActions::zcfsFeedsActions($this);
- dcCore::app()->callBehavior('zcfsFeedsActions', $this);
+ FeedsDefaultActions::addDefaultFeedsActions($this);
+
+ # --BEHAVIOR-- zoneclearFeedServerAddFeedsActions - FeedsActions
+ dcCore::app()->callBehavior('zoneclearFeedServerAddFeedsActions', $this);
}
- public function beginPage($breadcrumb = '', $head = '')
+ public function beginPage(string $breadcrumb = '', string $head = ''): void
{
echo
'' . __('Feeds server') . '' .
@@ -49,331 +67,39 @@ class zcfsFeedsActions extends dcActions
__('Back to feeds list') . '';
}
- public function endPage()
+ public function endPage(): void
{
echo '';
}
- public function error(Exception $e)
+ public function error(Exception $e): void
{
dcCore::app()->error->add($e->getMessage());
$this->beginPage(
dcPage::breadcrumb([
- html::escapeHTML(dcCore::app()->blog->name) => '',
- $this->getCallerTitle() => $this->getRedirection(true),
- __('Feeds actions') => '',
+ Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
+ $this->getCallerTitle() => $this->getRedirection(true),
+ __('Feeds actions') => '',
])
);
$this->endPage();
}
- protected function fetchEntries($from)
+ protected function fetchEntries(ArrayObject $from): void
{
- if (!empty($from['feeds'])) {
- $params['feed_id'] = $from['feeds'];
+ if (!empty($from['feeds']) && is_array($from['feeds'])) {
+ $params = [
+ 'feed_id' => $from['feeds'],
+ ];
- $feeds = $this->zcfs->getFeeds($params);
+ $feeds = ZoneclearFeedServer::instance()->getFeeds($params);
while ($feeds->fetch()) {
- $this->entries[$feeds->feed_id] = $feeds->feed_name;
+ $row = new FeedRow($feeds);
+ $this->entries[$row->id] = $row->name;
}
$this->rs = $feeds;
} else {
- $this->rs = dcCore::app()->con->select(
- 'SELECT blog_id FROM ' . dcCore::app()->prefix . dcBlog::BLOG_TABLE_NAME . ' WHERE false'
- );
- }
- }
-}
-
-/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Feeds server - Default actions methods
- * @since 2.6
- * @see dcDefaultPostsActionsPage for mor info
- */
-class zcfsDefaultFeedsActions
-{
- public static function zcfsFeedsActions(zcfsFeedsActions $ap)
- {
- $ap->addAction(
- [__('Change category') => 'changecat'],
- ['zcfsDefaultFeedsActions', 'doChangeCategory']
- );
- $ap->addAction(
- [__('Change update interval') => 'changeint'],
- ['zcfsDefaultFeedsActions', 'doChangeInterval']
- );
- $ap->addAction(
- [__('Disable feed update') => 'disablefeed'],
- ['zcfsDefaultFeedsActions', 'doEnableFeed']
- );
- $ap->addAction(
- [__('Enable feed update') => 'enablefeed'],
- ['zcfsDefaultFeedsActions', 'doEnableFeed']
- );
- $ap->addAction(
- [__('Reset last update') => 'resetupdlast'],
- ['zcfsDefaultFeedsActions', 'doResetUpdate']
- );
- $ap->addAction(
- [__('Update (check) feed') => 'updatefeed'],
- ['zcfsDefaultFeedsActions', 'doUpdateFeed']
- );
- $ap->addAction(
- [__('Delete related posts') => 'deletepost'],
- ['zcfsDefaultFeedsActions', 'doDeletePost']
- );
- $ap->addAction(
- [__('Delete feed (without related posts)') => 'deletefeed'],
- ['zcfsDefaultFeedsActions', 'doDeleteFeed']
- );
- }
-
- public static function doEnableFeed(zcfsFeedsActions $ap, $post)
- {
- $enable = $ap->getAction() == 'enablefeed';
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- foreach ($ids as $id) {
- $ap->zcfs->enableFeed($id, $enable);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- $enable ?
- __(
- '%d feed has been successfully enabled.',
- '%d feeds have been successfully enabled.',
- count($ids)
- )
- :
- __(
- '%d feed has been successfully disabled.',
- '%d feeds have been successfully disabled.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- }
-
- public static function doDeletePost(zcfsFeedsActions $ap, $post)
- {
- $types = [
- 'zoneclearfeed_url',
- 'zoneclearfeed_author',
- 'zoneclearfeed_site',
- 'zoneclearfeed_sitename',
- 'zoneclearfeed_id',
- ];
-
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- foreach ($ids as $id) {
- $posts = $ap->zcfs->getPostsByFeed([
- 'feed_id' => $id,
- ]);
-
- while ($posts->fetch()) {
- dcCore::app()->blog->delPost($posts->post_id);
- dcCore::app()->con->execute(
- 'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
- 'WHERE post_id = ' . $posts->post_id . ' ' .
- 'AND meta_type ' . dcCore::app()->con->in($types) . ' '
- );
- }
- }
-
- dcAdminNotices::addSuccessNotice(
- __('Entries have been successfully deleted.')
- );
- $ap->redirect(true);
- }
-
- public static function doDeleteFeed(zcfsFeedsActions $ap, $post)
- {
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- foreach ($ids as $id) {
- $ap->zcfs->delFeed($id);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- __(
- '%d feed has been successfully deleted.',
- '%d feeds have been successfully deleted.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- }
-
- public static function doUpdateFeed(zcfsFeedsActions $ap, $post)
- {
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- foreach ($ids as $id) {
- $ap->zcfs->checkFeedsUpdate($id, true);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- __(
- '%d feed has been successfully updated.',
- '%d feeds have been successfully updated.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- }
-
- public static function doResetUpdate(zcfsFeedsActions $ap, $post)
- {
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->feed_upd_last = 0;
- $ap->zcfs->updFeed($id, $cur);
- $ap->zcfs->checkFeedsUpdate($id, true);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- __(
- 'Last update of %s feed successfully reseted.',
- 'Last update of %s feeds successfully reseted.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- }
-
- public static function doChangeCategory(zcfsFeedsActions $ap, $post)
- {
- if (isset($post['upd_cat_id'])) {
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- $cat_id = abs((int) $post['upd_cat_id']);
-
- foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->cat_id = $cat_id == 0 ? null : $cat_id;
- $ap->zcfs->updFeed($id, $cur);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- __(
- 'Category of %s feed successfully changed.',
- 'Category of %s feeds successfully changed.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- } else {
- $categories_combo = dcAdminCombos::getCategoriesCombo(
- dcCore::app()->blog->getCategories()
- );
-
- $ap->beginPage(
- dcPage::breadcrumb([
- html::escapeHTML(dcCore::app()->blog->name) => '',
- __('Feeds server') => '',
- $ap->getCallerTitle() => $ap->getRedirection(true),
- __('Change category for this selection') => '',
- ])
- );
-
- echo
- '';
-
- $ap->endPage();
- }
- }
-
- public static function doChangeInterval(zcfsFeedsActions $ap, $post)
- {
- if (isset($post['upd_upd_int'])) {
- $ids = $ap->getIDs();
-
- if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
- }
-
- $upd_int = abs((int) $post['upd_upd_int']);
-
- foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->feed_upd_int = $upd_int;
- $ap->zcfs->updFeed($id, $cur);
- }
-
- dcAdminNotices::addSuccessNotice(sprintf(
- __(
- 'Update frequency of %s feed successfully changed.',
- 'Update frequency of %s feeds successfully changed.',
- count($ids)
- ),
- count($ids)
- ));
- $ap->redirect(true);
- } else {
- $ap->beginPage(
- dcPage::breadcrumb(
- [
- html::escapeHTML(dcCore::app()->blog->name) => '',
- __('Feeds server') => '',
- $ap->getCallerTitle() => $ap->getRedirection(true),
- __('Change update frequency for this selection') => '',
- ]
- )
- );
-
- echo
- '';
-
- $ap->endPage();
+ $this->rs = MetaRecord::newFromArray([]);
}
}
}
diff --git a/src/FeedsDefaultActions.php b/src/FeedsDefaultActions.php
index 88d1cb8..b154062 100644
--- a/src/FeedsDefaultActions.php
+++ b/src/FeedsDefaultActions.php
@@ -10,68 +10,90 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use dcCore;
+use dcMeta;
+use dcPage;
+use Dotclear\Database\Statement\DeleteStatement;
+use Dotclear\Helper\Html\Form\{
+ Form,
+ Hidden,
+ Label,
+ Para,
+ Select,
+ Submit,
+ Text
+};
+use Dotclear\Helper\Html\Html;
+use Exception;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Feeds server - Default actions methods
- * @since 2.6
- * @see dcDefaultPostsActionsPage for mor info
+ * Backend feeds list default actions.
*/
-class zcfsDefaultFeedsActions
+class FeedsDefaultActions
{
- public static function zcfsFeedsActions(zcfsFeedsActions $ap)
+ /**
+ * Add feeds list actions.
+ */
+ public static function addDefaultFeedsActions(FeedsActions $ap): void
{
$ap->addAction(
[__('Change category') => 'changecat'],
- ['zcfsDefaultFeedsActions', 'doChangeCategory']
+ [self::class, 'doChangeCategory']
);
$ap->addAction(
[__('Change update interval') => 'changeint'],
- ['zcfsDefaultFeedsActions', 'doChangeInterval']
+ [self::class, 'doChangeInterval']
);
$ap->addAction(
[__('Disable feed update') => 'disablefeed'],
- ['zcfsDefaultFeedsActions', 'doEnableFeed']
+ [self::class, 'doEnableFeed']
);
$ap->addAction(
[__('Enable feed update') => 'enablefeed'],
- ['zcfsDefaultFeedsActions', 'doEnableFeed']
+ [self::class, 'doEnableFeed']
);
$ap->addAction(
[__('Reset last update') => 'resetupdlast'],
- ['zcfsDefaultFeedsActions', 'doResetUpdate']
+ [self::class, 'doResetUpdate']
);
$ap->addAction(
[__('Update (check) feed') => 'updatefeed'],
- ['zcfsDefaultFeedsActions', 'doUpdateFeed']
+ [self::class, 'doUpdateFeed']
);
$ap->addAction(
[__('Delete related posts') => 'deletepost'],
- ['zcfsDefaultFeedsActions', 'doDeletePost']
+ [self::class, 'doDeletePost']
);
$ap->addAction(
[__('Delete feed (without related posts)') => 'deletefeed'],
- ['zcfsDefaultFeedsActions', 'doDeleteFeed']
+ [self::class, 'doDeleteFeed']
);
}
- public static function doEnableFeed(zcfsFeedsActions $ap, $post)
+ /**
+ * Enable / disable feeds.
+ */
+ public static function doEnableFeed(FeedsActions $ap, ArrayObject $post): void
{
$enable = $ap->getAction() == 'enablefeed';
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
foreach ($ids as $id) {
$ap->zcfs->enableFeed($id, $enable);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
$enable ?
__(
'%d feed has been successfully enabled.',
@@ -89,20 +111,25 @@ class zcfsDefaultFeedsActions
$ap->redirect(true);
}
- public static function doDeletePost(zcfsFeedsActions $ap, $post)
+ /**
+ * Delete feeds posts.
+ */
+ public static function doDeletePost(FeedsActions $ap, ArrayObject $post): void
{
$types = [
- 'zoneclearfeed_url',
- 'zoneclearfeed_author',
- 'zoneclearfeed_site',
- 'zoneclearfeed_sitename',
- 'zoneclearfeed_id',
+ My::META_PREFIX . 'url',
+ My::META_PREFIX . 'author',
+ My::META_PREFIX . 'site',
+ My::META_PREFIX . 'sitename',
+ My::META_PREFIX . 'id',
];
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
foreach ($ids as $id) {
@@ -111,34 +138,42 @@ class zcfsDefaultFeedsActions
]);
while ($posts->fetch()) {
- dcCore::app()->blog->delPost($posts->post_id);
- dcCore::app()->con->execute(
- 'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
- 'WHERE post_id = ' . $posts->post_id . ' ' .
- 'AND meta_type ' . dcCore::app()->con->in($types) . ' '
- );
+ if (is_numeric($posts->f('post_id'))) {
+ dcCore::app()->blog?->delPost((int) $posts->f('post_id'));
+ $sql = new DeleteStatement();
+ $sql
+ ->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
+ ->where('post_id = ' . $posts->f('post_id'))
+ ->and('meta_type ' . $sql->in($types))
+ ->delete();
+ }
}
}
- dcAdminNotices::addSuccessNotice(
+ dcPage::addSuccessNotice(
__('Entries have been successfully deleted.')
);
$ap->redirect(true);
}
- public static function doDeleteFeed(zcfsFeedsActions $ap, $post)
+ /**
+ * Delete feeds.
+ */
+ public static function doDeleteFeed(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
foreach ($ids as $id) {
- $ap->zcfs->delFeed($id);
+ $ap->zcfs->deleteFeed($id);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
__(
'%d feed has been successfully deleted.',
'%d feeds have been successfully deleted.',
@@ -149,116 +184,148 @@ class zcfsDefaultFeedsActions
$ap->redirect(true);
}
- public static function doUpdateFeed(zcfsFeedsActions $ap, $post)
+ /**
+ * Update feeds properties.
+ */
+ public static function doUpdateFeed(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
foreach ($ids as $id) {
$ap->zcfs->checkFeedsUpdate($id, true);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
__('%d feed has been successfully updated.', '%d feeds have been successfully updated.', count($ids)),
count($ids)
));
$ap->redirect(true);
}
- public static function doResetUpdate(zcfsFeedsActions $ap, $post)
+ /**
+ * Reset feeds update timer.
+ */
+ public static function doResetUpdate(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
+ $cur = $ap->zcfs->openCursor();
foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->feed_upd_last = 0;
- $ap->zcfs->updFeed($id, $cur);
+ $cur->clean();
+ $cur->setField('feed_upd_last', 0);
+ $ap->zcfs->updateFeed($id, $cur);
$ap->zcfs->checkFeedsUpdate($id, true);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
__('Last update of %s feed successfully reseted.', 'Last update of %s feeds successfully reseted.', count($ids)),
count($ids)
));
$ap->redirect(true);
}
- public static function doChangeCategory(zcfsFeedsActions $ap, $post)
+ /**
+ * Change feeds categories.
+ */
+ public static function doChangeCategory(FeedsActions $ap, ArrayObject $post): void
{
if (isset($post['upd_cat_id'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
- $cat_id = abs((int) $post['upd_cat_id']);
+ $cat_id = is_numeric($post['upd_cat_id']) ? abs((int) $post['upd_cat_id']) : null;
+ $cur = $ap->zcfs->openCursor();
foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->cat_id = $cat_id == 0 ? null : $cat_id;
- $ap->zcfs->updFeed($id, $cur);
+ $cur->clean();
+ $cur->setField('cat_id', $cat_id == 0 ? null : $cat_id);
+ $ap->zcfs->updateFeed($id, $cur);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
__('Category of %s feed successfully changed.', 'Category of %s feeds successfully changed.', count($ids)),
count($ids)
));
$ap->redirect(true);
} else {
- $categories_combo = dcAdminCombos::getCategoriesCombo(
- dcCore::app()->blog->getCategories()
- );
-
$ap->beginPage(
dcPage::breadcrumb([
- html::escapeHTML(dcCore::app()->blog->name) => '',
- __('Feeds server') => '',
- $ap->getCallerTitle() => $ap->getRedirection(true),
- __('Change category for this selection') => '',
+ Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
+ __('Feeds server') => '',
+ $ap->getCallerTitle() => $ap->getRedirection(true),
+ __('Change category for this selection') => '',
])
);
echo
- '';
+ (new Form('form-action'))
+ ->method('post')
+ ->action($ap->getURI())
+ ->fields([
+ (new Text('', $ap->getCheckboxes())),
+ (new Para())
+ ->items(array_merge(
+ $ap->hiddenFields(),
+ [
+ (new Label(__('Category:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('upd_cat_id'),
+ (new Select('upd_cat_id'))
+ ->items(Combo::postCategories()),
+ (new Submit('do-action'))
+ ->value(__('Save')),
+ (new Hidden(['action'], 'changecat')),
+ dcCore::app()->formNonce(false),
+ ]
+ )),
+
+ ])
+ ->render();
$ap->endPage();
}
}
- public static function doChangeInterval(zcfsFeedsActions $ap, $post)
+ /**
+ * Change feeds update interval.
+ */
+ public static function doChangeInterval(FeedsActions $ap, ArrayObject $post): void
{
if (isset($post['upd_upd_int'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
- throw new Exception(__('No feeds selected'));
+ $ap->error(new Exception(__('No feeds selected')));
+
+ return;
}
- $upd_int = abs((int) $post['upd_upd_int']);
+ $upd_int = is_numeric($post['upd_upd_int']) ? abs((int) $post['upd_upd_int']) : 0;
+ $cur = $ap->zcfs->openCursor();
foreach ($ids as $id) {
- $cur = $ap->zcfs->openCursor();
- $cur->feed_upd_int = $upd_int;
- $ap->zcfs->updFeed($id, $cur);
+ $cur->clean();
+ $cur->setField('feed_upd_int', $upd_int);
+ $ap->zcfs->updateFeed($id, $cur);
}
- dcAdminNotices::addSuccessNotice(sprintf(
+ dcPage::addSuccessNotice(sprintf(
__('Update frequency of %s feed successfully changed.', 'Update frequency of %s feeds successfully changed.', count($ids)),
count($ids)
));
@@ -267,24 +334,37 @@ class zcfsDefaultFeedsActions
$ap->beginPage(
dcPage::breadcrumb(
[
- html::escapeHTML(dcCore::app()->blog->name) => '',
- __('Feeds server') => '',
- $ap->getCallerTitle() => $ap->getRedirection(true),
- __('Change update frequency for this selection') => '',
+ Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
+ __('Feeds server') => '',
+ $ap->getCallerTitle() => $ap->getRedirection(true),
+ __('Change update frequency for this selection') => '',
]
)
);
echo
- '';
+ (new Form('form-action'))
+ ->method('post')
+ ->action($ap->getURI())
+ ->fields([
+ (new Text('', $ap->getCheckboxes())),
+ (new Para())
+ ->items(array_merge(
+ $ap->hiddenFields(),
+ [
+ (new Label(__('Frequency:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('upd_upd_int'),
+ (new Select('upd_upd_int'))
+ ->items(Combo::updateInterval()),
+ (new Submit('do-action'))
+ ->value(__('Save')),
+ (new Hidden(['action'], 'changeint')),
+ dcCore::app()->formNonce(false),
+ ]
+ )),
+
+ ])
+ ->render();
$ap->endPage();
}
diff --git a/src/FeedsList.php b/src/FeedsList.php
index 5453e1c..ff99440 100644
--- a/src/FeedsList.php
+++ b/src/FeedsList.php
@@ -10,132 +10,179 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use adminGenericFilterV2;
+use adminGenericListV2;
+use dcCore;
+use dcPager;
+use Dotclear\Helper\Date;
+use Dotclear\Helper\Html\Form\{
+ Checkbox,
+ Div,
+ Link,
+ Para,
+ Text
+};
+use Dotclear\Helper\Html\Html;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Feeds server - feeds list methods
- * @since 2.6
- * @see adminGenericList for more info
+ * Backend feeds list.
*/
-class zcfsFeedsList extends adminGenericList
+class FeedsList extends adminGenericListV2
{
- private $zc = null;
-
- public function feedsDisplay($page, $nb_per_page, $enclose_block = '', $filter = false)
+ public function display(adminGenericFilterV2 $filter, string $enclose_block = ''): void
{
if ($this->rs->isEmpty()) {
- if ($filter) {
- echo '' . __('No feeds matches the filter') . '
';
- } else {
- echo '' . __('No feeds') . '
';
- }
- } else {
- $this->zc = new zoneclearFeedServer();
- $pager = new dcPager($page, $this->rs_count, $nb_per_page, 10);
- $entries = [];
- if (isset($_REQUEST['feeds'])) {
- foreach ($_REQUEST['feeds'] as $v) {
- $entries[(int) $v] = true;
- }
- }
- $html_block = '' .
- '
' .
- '' . (
- $filter ?
- sprintf(__('List of %s feeds matching the filter.'), $this->rs_count) :
- sprintf(__('List of feeds (%s)'), $this->rs_count)
- ) . '';
+ echo
+ (new Text(
+ 'p',
+ $filter->show() ?
+ __('No feeds matches the filter') :
+ __('No feeds')
+ ))
+ ->class('info')
+ ->render();
- $cols = [
- 'title' => '' . __('Name') . ' | ',
- 'desc' => '' . __('Feed') . ' | ',
- 'period' => '' . __('Frequency') . ' | ',
- 'update' => '' . __('Last update') . ' | ',
- 'entries' => '' . __('Entries') . ' | ',
- 'status' => '' . __('Status') . ' | ',
- ];
- $cols = new ArrayObject($cols);
-
- dcCore::app()->callBehavior('adminZcfsFeedsListHeader', $this->rs, $cols);
-
- $this->userColumns('zcfs_feeds', $cols);
-
- $html_block .= '' . implode(iterator_to_array($cols)) . '
%s
%s
';
- if ($enclose_block) {
- $html_block = sprintf($enclose_block, $html_block);
- }
-
- echo $pager->getLinks();
-
- $blocks = explode('%s', $html_block);
-
- echo $blocks[0];
-
- while ($this->rs->fetch()) {
- echo $this->feedsLine(isset($entries[$this->rs->feed_id]));
- }
-
- echo $blocks[1];
- echo $blocks[2];
- echo $pager->getLinks();
+ return;
}
+
+ $page = is_numeric($filter->value('page')) ? (int) $filter->value('page') : 1;
+ $nbpp = is_numeric($filter->value('nb')) ? (int) $filter->value('nb') : 10;
+ $count = (int) $this->rs_count;
+ $pager = new dcPager($page, $count, $nbpp, 10);
+
+ $cols = new ArrayObject([
+ 'title' => (new Text('th', __('Name')))
+ ->class('first')
+ ->extra('colspan="2"'),
+ 'desc' => (new Text('th', __('Feed')))
+ ->extra('scope="col"'),
+ 'period' => (new Text('th', __('Frequency')))
+ ->extra('scope="col"'),
+ 'update' => (new Text('th', __('Last update')))
+ ->extra('scope="col"')->class('nowrap'),
+ 'entries' => (new Text('th', __('Entries')))
+ ->extra('scope="col"'),
+ 'status' => (new Text('th', __('Status')))
+ ->extra('scope="col"'),
+ ]);
+
+ $this->userColumns(My::id() . 'feeds', $cols);
+
+ $lines = [];
+ while ($this->rs->fetch()) {
+ $lines[] = $this->line(isset($_POST['feeds']) && in_array($this->rs->post_id, $_POST['feeds']));
+ }
+
+ echo
+ $pager->getLinks() .
+ sprintf(
+ $enclose_block,
+ (new Div())
+ ->class('table-outer')
+ ->items([
+ (new Para(null, 'table'))
+ ->items([
+ (new Text(
+ 'caption',
+ $filter->show() ?
+ sprintf(__('List of %s feeds matching the filter.'), $this->rs_count) :
+ sprintf(__('List of feeds. (%s)'), $this->rs_count)
+ )),
+ (new Para(null, 'tr'))
+ ->items(iterator_to_array($cols)),
+ (new Para(null, 'tbody'))
+ ->items($lines),
+ ]),
+ ])
+ ->render()
+ ) .
+ $pager->getLinks();
}
- private function feedsLine($checked)
+ private function line(bool $checked): Para
{
- $combo_status = zoneclearFeedServer::getAllStatus();
- $combo_upd_int = zoneclearFeedServer::getAllUpdateInterval();
- $status = $this->rs->feed_status ?
- '' :
- '';
+ $row = new FeedRow($this->rs);
+ $img_title = $row->status ? __('enabled') : __('disabled');
+ $img_src = $row->status ? 'check-on.png' : 'check-off.png';
- $entries_count = $this->zc->getPostsByFeed(['feed_id' => $this->rs->feed_id], true)->f(0);
- $shunk_feed = $this->rs->feed_feed;
+ $entries_count = ZoneclearFeedServer::instance()->getPostsByFeed(['feed_id' => $row->id], true)->f(0);
+ if (!is_numeric($entries_count)) {
+ $entries_count = 0;
+ }
+
+ $shunk_feed = $row->feed;
if (strlen($shunk_feed) > 83) {
$shunk_feed = substr($shunk_feed, 0, 50) . '...' . substr($shunk_feed, -20);
}
- $url = dcCore::app()->adminurl->get('admin.plugin.' . basename(dirname('../' . __DIR__)), ['part' => 'feed', 'feed_id' => $this->rs->feed_id]);
+ $url = dcCore::app()->adminurl?->get('admin.plugin.' . My::id(), ['part' => 'feed', 'feed_id' => $row->id]);
+ if (!is_string($url)) {
+ $url = '';
+ }
+ $tz = dcCore::app()->auth?->getInfo('user_tz');
+ if (!is_string($tz)) {
+ $tz = 'UTC';
+ }
- $cols = [
- 'check' => '' .
- form::checkbox(['feeds[]'], $this->rs->feed_id, ['checked' => $checked]) .
- ' | ',
- 'title' => '' .
- '' . html::escapeHTML($this->rs->feed_name) . '' .
- ' | ',
- 'desc' => '' .
- '' . html::escapeHTML($shunk_feed) . '' .
- ' | ',
- 'period' => '' .
- array_search($this->rs->feed_upd_int, $combo_upd_int) .
- ' | ',
- 'update' => '' .
- (
- $this->rs->feed_upd_last < 1 ?
+ $cols = new ArrayObject([
+ 'check' => (new Para(null, 'td'))
+ ->class('nowrap minimal')
+ ->items([
+ (new Checkbox(['feeds[]'], $checked))
+ ->value($row->id),
+ ]),
+ 'title' => (new Para(null, 'td'))
+ ->class('nowrap')
+ ->items([
+ (new Link())
+ ->title(__('Edit'))
+ ->text(Html::escapeHTML($row->name))
+ ->href($url . '#feed'),
+ ]),
+ 'desc' => (new Para(null, 'td'))
+ ->class('nowrap maximal')
+ ->items([
+ (new Link())
+ ->title(Html::escapeHTML($row->desc))
+ ->text(Html::escapeHTML($shunk_feed))
+ ->href($row->feed),
+ ])
+ ->class('nowrap minimal'),
+ 'period' => (new Text('td', (string) array_search($row->upd_int, Combo::updateInterval())))
+ ->class('nowrap minimal'),
+ 'update' => (new Text(
+ 'td',
+ $row->upd_last < 1 ?
__('never') :
- dt::str(__('%Y-%m-%d %H:%M'), (int) $this->rs->feed_upd_last, dcCore::app()->auth->getInfo('user_tz'))
- ) . ' | ',
- 'entries' => '' .
- (
- $entries_count ?
- '' . $entries_count . '' :
- $entries_count
- ) . ' | ',
- 'status' => '' . $status . ' | ',
- ];
+ Date::str(__('%Y-%m-%d %H:%M'), $row->upd_last, $tz)
+ ))
+ ->class('nowrap minimal'),
+ 'entries' => (new Para(null, 'td'))
+ ->class('nowrap minimal count')
+ ->items([
+ (new Link())
+ ->title(Html::escapeHTML(__('View entries')))
+ ->text(Html::escapeHTML((string) $entries_count))
+ ->href($url . '#entries'),
+ ]),
+ 'status' => (new Para(null, 'td'))
+ ->class('nowrap minimal status')
+ ->items([
+ (new Text('img', ''))
+ ->title($img_title)
+ ->extra('src="images/' . $img_src . '"'),
+ ]),
+ ]);
- $cols = new ArrayObject($cols);
- dcCore::app()->callBehavior('adminZcfsFeedsListValue', $this->rs, $cols);
+ $this->userColumns(My::id() . 'feeds', $cols);
- $this->userColumns('zcfs_feeds', $cols);
-
- return
- '' .
- implode(iterator_to_array($cols)) .
- '
';
+ return (new Para('p' . $row->id, 'tr'))
+ ->class('line' . ($row->status != 1 ? ' offline ' : ''))
+ ->items(iterator_to_array($cols));
}
}
diff --git a/src/Frontend.php b/src/Frontend.php
index 4e42445..06632e4 100644
--- a/src/Frontend.php
+++ b/src/Frontend.php
@@ -10,71 +10,109 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
+declare(strict_types=1);
-# Namespace for settings
-dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
-$s = dcCore::app()->blog->settings->__get(basename(__DIR__));
+namespace Dotclear\Plugin\zoneclearFeedServer;
-# Widgets
-require_once __DIR__ . '/_widgets.php';
+use dcCore;
+use dcNsProcess;
+use dcUtils;
+use Dotclear\Database\MetaRecord;
+use Dotclear\Helper\Html\Html;
+use Exception;
-dcCore::app()->addBehavior('coreBlogGetPosts', ['zcfsPublicBehaviors', 'coreBlogGetPosts']);
+/**
+ * Frontend prepend.
+ */
+class Frontend extends dcNsProcess
+{
+ public static function init(): bool
+ {
+ static::$init = My::phpCompliant();
-if (!$s->active) {
- return null;
-}
-if (1 == $s->bhv_pub_upd) {
- dcCore::app()->addBehavior('publicBeforeDocumentV2', ['zcfsPublicBehaviors', 'publicDocumentV2']);
-} elseif (2 == $s->bhv_pub_upd) {
- dcCore::app()->addBehavior('publicAfterDocumentV2', ['zcfsPublicBehaviors', 'publicAfterDocumentV2']);
-} elseif (3 == $s->bhv_pub_upd) {
- dcCore::app()->addBehavior('publicHeadContent', ['zcfsPublicBehaviors', 'publicHeadContent']);
-}
-
-# Take care about tweakurls (thanks Mathieu M.)
-if (version_compare(dcCore::app()->plugins->moduleInfo('tweakurls', 'version'), '0.8', '>=')) {
- dcCore::app()->addbehavior('zoneclearFeedServerAfterPostCreate', ['zoneclearFeedServer', 'tweakurlsAfterPostCreate']);
-}
-
-# Register tempalte blocks
-$tpl_blocks = [
- 'Feeds',
- 'FeedsFooter',
- 'FeedsHeader',
- 'FeedIf',
-];
-foreach ($tpl_blocks as $v) {
- dcCore::app()->tpl->addBlock('zc' . $v, ['zcfsTemplate', $v]);
-}
-
-# Register tempalte values
-$tpl_values = [
- 'FeedsCount',
- 'FeedsEntriesCount',
- 'FeedEntriesCount',
- 'FeedCategory',
- 'FeedCategoryID',
- 'FeedCategoryURL',
- 'FeedCategoryShortURL',
- 'FeedID',
- 'FeedIfFirst',
- 'FeedIfOdd',
- 'FeedLang',
- 'FeedName',
- 'FeedOwner',
- 'FeedDesc',
- 'FeedSiteURL',
- 'FeedFeedURL',
-];
-foreach ($tpl_values as $v) {
- dcCore::app()->tpl->addValue('zc' . $v, ['zcfsTemplate', $v]);
-}
-
-dcCore::app()->addBehavior('publicBreadcrumb', function ($context, $separator) {
- if ($context == 'zoneclearFeedsPage') {
- return __('List of feeds');
+ return static::$init;
}
-});
+
+ public static function process(): bool
+ {
+ if (!static::$init) {
+ return false;
+ }
+
+ $s = ZoneclearFeedServer::instance()->settings;
+
+ dcCore::app()->addBehaviors([
+ // posts record
+ 'coreBlogGetPosts' => function (MetaRecord $rs): void {
+ RsExtPosts::$brother_extensions = $rs->extensions();
+ $rs->extend(RsExtPosts::class);
+ },
+ // breadcrumb
+ 'publicBreadcrumb' => function (string $context, string $separator): string {
+ return $context == 'zoneclearFeedsPage' ? __('List of feeds') : '';
+ },
+ // widgets registration
+ 'initWidgets' => [Widgets::class, 'init'],
+ ]);
+
+ // Register template blocks
+ foreach (My::TPL_BLOCKS as $block) {
+ dcCore::app()->tpl->addBlock('zc' . $block, [Template::class, $block]);
+ }
+
+ // Register template values
+ foreach (My::TPL_VALUES as $value) {
+ dcCore::app()->tpl->addValue('zc' . $value, [Template::class, $value]);
+ }
+
+ // module not active
+ if (!$s->active) {
+ return true;
+ }
+
+ // feeds update methods
+ if (1 == $s->bhv_pub_upd) {
+ dcCore::app()->addBehavior('publicBeforeDocumentV2', function (): void {
+ if (in_array(dcCore::app()->url->type, ['default', 'feed'])) {
+ try {
+ ZoneclearFeedServer::instance()->checkFeedsUpdate();
+ } catch (Exception $e) {
+ }
+ };
+ });
+ } elseif (2 == $s->bhv_pub_upd) {
+ dcCore::app()->addBehavior('publicAfterDocumentV2', function (): void {
+ try {
+ ZoneclearFeedServer::instance()->checkFeedsUpdate();
+ } catch (Exception $e) {
+ pdump($e);
+ }
+ });
+ } elseif (3 == $s->bhv_pub_upd) {
+ dcCore::app()->addBehavior('publicHeadContent', function (): void {
+ if (is_null(dcCore::app()->blog) || dcCore::app()->url->type != 'default') {
+ return;
+ }
+
+ $blog_url = Html::escapeJS(
+ dcCore::app()->blog->url .
+ dcCore::app()->url->getBase('zoneclearFeedsPage') .
+ '/zcfsupd'
+ );
+ $blog_id = Html::escapeJS(dcCore::app()->blog->id);
+
+ echo
+ "\n \n" .
+ dcUtils::jsLoad(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage') . '/zcfsupd.js') .
+ "\n";
+ });
+ }
+
+ return true;
+ }
+}
diff --git a/src/Install.php b/src/Install.php
index 902260b..a0713c3 100644
--- a/src/Install.php
+++ b/src/Install.php
@@ -10,70 +10,91 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
-try {
- // Check module version
- if (!dcCore::app()->newVersion(
- basename(__DIR__),
- dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version')
- )) {
- return null;
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use dcCore;
+use dcNsProcess;
+use Dotclear\Database\Structure;
+use Exception;
+
+/**
+ * Module installation.
+ */
+class Install extends dcNsProcess
+{
+ public static function init(): bool
+ {
+ if (defined('DC_CONTEXT_ADMIN') && My::phpCompliant()) {
+ $version = dcCore::app()->plugins->moduleInfo(My::id(), 'version');
+ static::$init = is_string($version) ? dcCore::app()->newVersion(My::id(), $version) : true;
+ }
+
+ return static::$init;
}
- // Upgrade existing install
- zcfsUpgrade::preUpgrade();
+ public static function process(): bool
+ {
+ if (!static::$init) {
+ return false;
+ }
- // Tables
- $t = new dbStruct(dcCore::app()->con, dcCore::app()->prefix);
- $t->{initZoneclearFeedServer::TABLE_NAME}
- ->feed_id('bigint', 0, false)
- ->feed_creadt('timestamp', 0, false, 'now()')
- ->feed_upddt('timestamp', 0, false, 'now()')
- ->feed_type('varchar', 32, false, "'feed'")
- ->blog_id('varchar', 32, false)
- ->cat_id('bigint', 0, true)
- ->feed_upd_int('integer', 0, false, 3600)
- ->feed_upd_last('integer', 0, false, 0)
- ->feed_status('smallint', 0, false, 0)
- ->feed_name('varchar', 255, false)
- ->feed_desc('text', null, true) //!pgsql reserved 'desc'
- ->feed_url('varchar', 255, false)
- ->feed_feed('varchar', 255, false)
- ->feed_tags('varchar', 255, true)
- ->feed_get_tags('smallint', 0, false, 1)
- ->feed_owner('varchar', 255, false)
- ->feed_tweeter('varchar', 64, false) // tweeter ident
- ->feed_lang('varchar', 5, true)
- ->feed_nb_out('integer', 0, false, 0)
- ->feed_nb_in('integer', 0, false, 0)
+ try {
+ // Upgrade existing install
+ Upgrade::preUpgrade();
- ->primary('pk_zcfs', 'feed_id')
- ->index('idx_zcfs_type', 'btree', 'feed_type')
- ->index('idx_zcfs_blog', 'btree', 'blog_id');
+ // Tables
+ $s = new Structure(dcCore::app()->con, dcCore::app()->prefix);
+ $s->__get(My::TABLE_NAME)
+ ->field('feed_id', 'bigint', 0, false)
+ ->field('feed_creadt', 'timestamp', 0, false, 'now()')
+ ->field('feed_upddt', 'timestamp', 0, false, 'now()')
+ ->field('feed_type', 'varchar', 32, false, "'feed'")
+ ->field('blog_id', 'varchar', 32, false)
+ ->field('cat_id', 'bigint', 0, true)
+ ->field('feed_upd_int', 'integer', 0, false, 3600)
+ ->field('feed_upd_last', 'integer', 0, false, 0)
+ ->field('feed_status', 'smallint', 0, false, 0)
+ ->field('feed_name', 'varchar', 255, false)
+ ->field('feed_desc', 'text', null, true) //!pgsql reserved 'desc'
+ ->field('feed_url', 'varchar', 255, false)
+ ->field('feed_feed', 'varchar', 255, false)
+ ->field('feed_tags', 'varchar', 255, true)
+ ->field('feed_get_tags', 'smallint', 0, false, 1)
+ ->field('feed_owner', 'varchar', 255, false)
+ ->field('feed_tweeter', 'varchar', 64, false) // tweeter ident
+ ->field('feed_lang', 'varchar', 5, true)
+ ->field('feed_nb_out', 'integer', 0, false, 0)
+ ->field('feed_nb_in', 'integer', 0, false, 0)
- $ti = new dbStruct(dcCore::app()->con, dcCore::app()->prefix);
- $changes = $ti->synchronize($t);
+ ->primary('pk_zcfs', 'feed_id')
+ ->index('idx_zcfs_type', 'btree', 'feed_type')
+ ->index('idx_zcfs_blog', 'btree', 'blog_id');
- // Settings
- dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
- $s = dcCore::app()->blog->settings->__get(basename(__DIR__));
- $s->put('active', false, 'boolean', 'Enable zoneclearBlogServer', false, true);
- $s->put('pub_active', false, 'boolean', 'Enable public page of list of feeds', false, true);
- $s->put('post_status_new', true, 'boolean', 'Enable auto publish new posts', false, true);
- $s->put('bhv_pub_upd', 2, 'string', 'Auto update on public side (disable/before/after)', false, true);
- $s->put('update_limit', 1, 'integer', 'Number of feeds to update at one time', false, true);
- $s->put('keep_empty_feed', false, 'boolean', 'Keep active empty feeds', false, true);
- $s->put('tag_case', 0, 'integer', 'How to transform imported tags', false, true);
- $s->put('user', '', 'string', 'User id that has right on post', false, true);
- $s->put('post_full_tpl', json_encode(['post', 'category', 'tag', 'archive']), 'string', 'List of templates types for full feed', false, true);
- $s->put('post_title_redir', json_encode(['feed']), 'string', 'List of templates types for redirection to original post', false, true);
+ (new Structure(dcCore::app()->con, dcCore::app()->prefix))->synchronize($s);
- return true;
-} catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
+ // Settings
+ $s = dcCore::app()->blog?->settings->get(My::id());
+ if (is_null($s)) {
+ return false;
+ }
+ $s->put('active', false, 'boolean', 'Enable zoneclearBlogServer', false, true);
+ $s->put('pub_active', false, 'boolean', 'Enable public page of list of feeds', false, true);
+ $s->put('post_status_new', true, 'boolean', 'Enable auto publish new posts', false, true);
+ $s->put('bhv_pub_upd', 2, 'string', 'Auto update on public side (disable/before/after)', false, true);
+ $s->put('update_limit', 1, 'integer', 'Number of feeds to update at one time', false, true);
+ $s->put('keep_empty_feed', false, 'boolean', 'Keep active empty feeds', false, true);
+ $s->put('tag_case', 0, 'integer', 'How to transform imported tags', false, true);
+ $s->put('user', '', 'string', 'User id that has right on post', false, true);
+ $s->put('post_full_tpl', ['post', 'category', 'tag', 'archive'], 'array', 'List of templates types for full feed', false, true);
+ $s->put('post_title_redir', ['feed'], 'array', 'List of templates types for redirection to original post', false, true);
- return false;
+ return true;
+ } catch (Exception $e) {
+ dcCore::app()->error->add($e->getMessage());
+
+ return false;
+ }
+ }
}
diff --git a/src/Manage.php b/src/Manage.php
index d10e7b7..44f5110 100644
--- a/src/Manage.php
+++ b/src/Manage.php
@@ -10,557 +10,204 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
-if (0 !== dcCore::app()->testVersion(
- basename(__DIR__),
- dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version')
-)) {
- return null;
-}
+namespace Dotclear\Plugin\zoneclearFeedServer;
-dcPage::check(dcCore::app()->auth->makePermissions([
- dcAuth::PERMISSION_CONTENT_ADMIN,
-]));
+use adminGenericFilterV2;
+use dcAdminFilters;
+use dcCore;
+use dcNsProcess;
+use dcPage;
+use Dotclear\Helper\Html\Form\{
+ Div,
+ Form,
+ Label,
+ Link,
+ Para,
+ Select,
+ Submit,
+ Text
+};
+use Exception;
-$zcfs = new zoneclearFeedServer();
+/**
+ * Backend feeds list manage page.
+ */
+class Manage extends dcNsProcess
+{
+ public static function init(): bool
+ {
+ static::$init == defined('DC_CONTEXT_ADMIN')
+ && My::phpCompliant()
+ && !is_null(dcCore::app()->auth)
+ && !is_null(dcCore::app()->blog)
+ && dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
+ dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
+ ]), dcCore::app()->blog->id);
-# Not configured
-if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
- || !dcCore::app()->blog->settings->__get(basename(__DIR__))->user
-) {
- echo
- '' . __('Feeds server') . '' .
- dcPage::breadcrumb([
- __('Plugins') => '',
- __('Feeds server') => '',
- ]) .
- dcPage::notices();
-
-############################################################
-#
-# One feed
-#
-############################################################
-} elseif (isset($_REQUEST['part']) && $_REQUEST['part'] == 'feed') {
- $feed_id = '';
- $feed_name = '';
- $feed_desc = '';
- $feed_owner = '';
- $feed_tweeter = '';
- $feed_url = '';
- $feed_feed = '';
- $feed_lang = dcCore::app()->auth->getInfo('user_lang');
- $feed_tags = '';
- $feed_get_tags = '0';
- $feed_cat_id = '';
- $feed_status = '0';
- $feed_upd_int = 3600;
-
- $can_view_page = true;
-
- $feed_headlink = '';
- $feed_link = '%s';
-
- $next_link = $prev_link = $next_headlink = $prev_headlink = null;
-
- # Combos
- $combo_langs = l10n::getISOcodes(true);
- $combo_status = $zcfs->getAllStatus();
- $combo_upd_int = $zcfs->getAllUpdateInterval();
- $combo_categories = ['-' => ''];
-
- try {
- $categories = dcCore::app()->blog->getCategories(['post_type' => 'post']);
- while ($categories->fetch()) {
- $combo_categories[
- str_repeat(' ', $categories->level - 1) .
- '• ' . html::escapeHTML($categories->cat_title)
- ] = $categories->cat_id;
+ // call period manage page
+ if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
+ static::$init = ManageFeed::init();
}
- } catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
+
+ return static::$init;
}
- # Get entry informations
- if (!empty($_REQUEST['feed_id'])) {
- $feed = $zcfs->getFeeds(['feed_id' => $_REQUEST['feed_id']]);
-
- if ($feed->isEmpty()) {
- dcCore::app()->error->add(__('This feed does not exist.'));
- $can_view_page = false;
- } else {
- $feed_id = $feed->feed_id;
- $feed_name = $feed->feed_name;
- $feed_desc = $feed->feed_desc;
- $feed_owner = $feed->feed_owner;
- $feed_tweeter = $feed->feed_tweeter;
- $feed_url = $feed->feed_url;
- $feed_feed = $feed->feed_feed;
- $feed_lang = $feed->feed_lang;
- $feed_tags = $feed->feed_tags;
- $feed_get_tags = $feed->feed_get_tags;
- $feed_cat_id = $feed->cat_id;
- $feed_status = $feed->feed_status;
- $feed_upd_int = $feed->feed_upd_int;
-
- $next_params = [
- 'sql' => 'AND feed_id < ' . $feed_id . ' ',
- 'limit' => 1,
- ];
- $next_rs = $zcfs->getFeeds($next_params);
- $prev_params = [
- 'sql' => 'AND feed_id > ' . $feed_id . ' ',
- 'limit' => 1,
- ];
- $prev_rs = $zcfs->getFeeds($prev_params);
-
- if (!$next_rs->isEmpty()) {
- $next_link = sprintf(
- $feed_link,
- $next_rs->feed_id,
- html::escapeHTML($next_rs->feed_name),
- __('next feed') . ' »'
- );
- $next_headlink = sprintf(
- $feed_headlink,
- 'next',
- html::escapeHTML($next_rs->feed_name),
- $next_rs->feed_id
- );
- }
-
- if (!$prev_rs->isEmpty()) {
- $prev_link = sprintf(
- $feed_link,
- $prev_rs->feed_id,
- html::escapeHTML($prev_rs->feed_name),
- '« ' . __('previous feed')
- );
- $prev_headlink = sprintf(
- $feed_headlink,
- 'previous',
- html::escapeHTML($prev_rs->feed_name),
- $prev_rs->feed_id
- );
- }
+ public static function process(): bool
+ {
+ if (!static::$init) {
+ return false;
}
+
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
+
+ // not configured
+ if (!$s->active || !$s->user) {
+ dcCore::app()->error->add(__('Module is not wel configured'));
+
+ return true;
+ }
+
+ // call period manage page
+ if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
+ return ManageFeed::process();
+ }
+
+ return true;
}
- if (!empty($_POST['action']) && $_POST['action'] == 'savefeed') {
- try {
- $feed_name = $_POST['feed_name'];
- $feed_desc = $_POST['feed_desc'];
- $feed_owner = $_POST['feed_owner'];
- $feed_tweeter = $_POST['feed_tweeter'];
- $feed_url = $_POST['feed_url'];
- $feed_feed = $_POST['feed_feed'];
- $feed_lang = $_POST['feed_lang'];
- $feed_tags = $_POST['feed_tags'];
- $feed_get_tags = empty($_POST['feed_get_tags']) ? 0 : 1;
- $feed_cat_id = $_POST['feed_cat_id'] !== '' ? $_POST['feed_cat_id'] : null;
- $feed_upd_int = $_POST['feed_upd_int'];
- if (isset($_POST['feed_status'])) {
- $feed_status = (int) $_POST['feed_status'];
- }
-
- $testfeed_params['feed_feed'] = $feed_feed;
- if ($feed_id) {
- $testfeed_params['sql'] = 'AND feed_id <> ' . $feed_id . ' ';
- }
- if ($zcfs->getFeeds($testfeed_params, true)->f(0)) {
- throw new Exception(__('Record with same feed URL already exists.'));
- }
- if (empty($feed_name)) {
- throw new Exception(__('You must provide a name.'));
- }
- if (empty($feed_owner)) {
- throw new Exception(__('You must provide an owner.'));
- }
- if (!zoneclearFeedServer::validateURL($feed_url)) {
- throw new Exception(__('You must provide valid site URL.'));
- }
- if (!zoneclearFeedServer::validateURL($feed_feed)) {
- throw new Exception(__('You must provide valid feed URL.'));
- }
- if (null === $feed_cat_id && !dcCore::app()->blog->getCategory((int) $feed_cat_id)) {
- throw new Exception(__('You must provide valid category.'));
- }
- } catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
+ public static function render(): void
+ {
+ if (!static::$init) {
+ return;
}
- }
- if (!empty($_POST['action']) && $_POST['action'] == 'savefeed' && !dcCore::app()->error->flag()) {
- $cur = $zcfs->openCursor();
- $cur->feed_name = $feed_name;
- $cur->feed_desc = $feed_desc;
- $cur->feed_owner = $feed_owner;
- $cur->feed_tweeter = $feed_tweeter;
- $cur->feed_url = $feed_url;
- $cur->feed_feed = $feed_feed;
- $cur->feed_lang = $feed_lang;
- $cur->feed_tags = $feed_tags;
- $cur->feed_get_tags = (int) $feed_get_tags;
- $cur->cat_id = null === $feed_cat_id ? null : (int) $feed_cat_id;
- $cur->feed_status = (int) $feed_status;
- $cur->feed_upd_int = (int) $feed_upd_int;
-
- # Update feed
- if ($feed_id) {
- try {
- # --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedUpdate
- dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedUpdate', $cur, $feed_id);
-
- $zcfs->updFeed($feed_id, $cur);
-
- # --BEHAVIOR-- adminAfterZoneclearFeedServerFeedUpdate
- dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedUpdate', $cur, $feed_id);
-
- dcAdminNotices::addSuccessNotice(
- __('Feed successfully updated.')
- );
- dcCore::app()->adminurl->redirect(
- 'admin.plugin.' . basename(__DIR__),
- ['part' => 'feed', 'feed_id' => $feed_id]
- );
- } catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
- }
- } else {
- try {
- # --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedCreate
- dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedCreate', $cur);
-
- $return_id = $zcfs->addFeed($cur);
-
- # --BEHAVIOR-- adminAfterZoneclearFeedServerFeedCreate
- dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedCreate', $cur, $return_id);
-
- dcAdminNotices::addSuccessNotice(
- __('Feed successfully created.')
- );
- dcCore::app()->adminurl->redirect(
- 'admin.plugin.' . basename(__DIR__),
- ['part' => 'feed', 'feed_id' => $return_id]
- );
- } catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
- }
+ if (is_null(dcCore::app()->adminurl)) {
+ return;
}
- }
- # Prepared entries list
- if ($feed_id && $can_view_page) {
- # action
- $posts_actions_page = new dcPostsActions(
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
+
+ // not configured
+ if (!$s->active || !$s->user) {
+ dcPage::openModule(My::id());
+
+ echo
+ dcPage::breadcrumb([
+ __('Plugins') => '',
+ My::name() => '',
+ ]) .
+ dcPage::notices();
+
+ dcPage::closeModule();
+
+ return;
+ }
+
+ // call feed manage page
+ if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
+ ManageFeed::render();
+
+ return;
+ }
+
+ // feeds actions
+ $feeds_actions_page = new FeedsActions(
'plugin.php',
- [
- 'p' => basename(__DIR__),
- 'part' => 'feed',
- 'feed_id' => $feed_id,
- '_ANCHOR' => 'entries',
- ]
+ ['p' => My::id(), 'part' => 'feeds']
);
- if ($posts_actions_page->process()) {
- return null;
+ if ($feeds_actions_page->process()) {
+ return;
}
- # filters
- $post_filter = new zcfsPostFilter();
- $post_filter->add('part', 'feed');
- $post_filter->add('feed_id', $feed_id);
- $params = $post_filter->params();
+ // feeds filters
+ $feeds_filter = new adminGenericFilterV2(My::id() . 'feeds');
+ $feeds_filter->add('part', 'feeds');
+ $feeds_filter->add(dcAdminFilters::getPageFilter());
+ $feeds_filter->add(dcAdminFilters::getSearchFilter());
+ $params = $feeds_filter->params();
- # lexical sort
- $sortby_lex = [
- // key in sorty_combo (see above) => field in SQL request
- 'post_title' => 'post_title',
- 'cat_title' => 'cat_title',
- 'user_id' => 'P.user_id', ];
-
- # --BEHAVIOR-- adminPostsSortbyLexCombo
- dcCore::app()->callBehavior('adminPostsSortbyLexCombo', [& $sortby_lex]);
-
- $params['no_content'] = true;
- $params['feed_id'] = $feed_id;
- $params['order'] = (array_key_exists($post_filter->sortby, $sortby_lex) ?
- dcCore::app()->con->lexFields($sortby_lex[$post_filter->sortby]) :
- $post_filter->sortby) . ' ' . $post_filter->order;
-
- # posts
+ // feeds list
try {
- $posts = $zcfs->getPostsByFeed($params);
- $counter = $zcfs->getPostsByFeed($params, true);
- $post_list = new zcfsEntriesList(
- dcCore::app(),
- $posts,
- $counter->f(0)
- );
+ $feeds = $z->getFeeds($params);
+ $feeds_counter = $z->getFeeds($params, true)->f(0);
+ $feeds_list = new FeedsList($feeds, $feeds_counter);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
- }
- # display
- echo
- '' . __('Feeds server') . '' .
- ($feed_id && isset($post_filter) && !dcCore::app()->error->flag() ?
- $post_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feed', 'feed_id' => $feed_id], '&') . '#entries') .
- dcPage::jsLoad(dcPage::getPF(basename(__DIR__) . '/js/list.js'))
- : '') .
- dcPage::jsPageTabs() .
- $next_headlink . "\n" . $prev_headlink .
+ dcPage::openModule(
+ My::id(),
+ (
+ isset($feeds_list) && !dcCore::app()->error->flag() ?
+ $feeds_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feeds'], '&')) .
+ dcPage::jsModuleLoad(My::id() . '/js/list.js')
+ : ''
+ ) .
+ dcPage::jsPageTabs()
+ );
- # --BEHAVIOR-- adminZoneclearFeedServerHeader
- dcCore::app()->callBehavior('adminZoneclearFeedServerHeader') .
+ echo
+ dcPage::breadcrumb([
+ __('Plugins') => '',
+ My::name() => dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
+ __('Feeds list') => '',
+ ]) .
+ dcPage::notices();
- '' .
+ echo
+ (new Para())
+ ->class('top-add')
+ ->items([
+ (new Link())
+ ->class('button add')
+ ->text(__('New feed'))
+ ->href((string) dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed'])),
+ ])
+ ->render();
- dcPage::breadcrumb([
- __('Plugins') => '',
- __('Feeds server') => dcCore::app()->admin->getPageURL(),
- ($feed_id ? __('Edit feed') : __('New feed')) => '',
- ]) .
- dcPage::notices() .
- ($feed_id ? '' . sprintf(__('Edit feed "%s"'), $feed_name) . '
' : '');
+ if (isset($feeds_list)) {
+ $feeds_filter->display(
+ 'admin.plugin.' . My::id(),
+ dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . My::id(), ['part' => 'feeds'])
+ );
- # Feed
- if ($can_view_page) {
- # nav link
- if ($feed_id && ($next_link || $prev_link)) {
- echo '';
- if ($prev_link) {
- echo $prev_link;
- }
- if ($next_link && $prev_link) {
- echo ' | ';
- }
- if ($next_link) {
- echo $next_link;
- }
- echo '
';
+ $feeds_list->display(
+ $feeds_filter,
+ (new Form('form-feeds'))
+ ->method('post')
+ ->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feeds']))
+ ->fields([
+ (new Text('', '%s')),
+ (new Div())
+ ->class('two-cols')
+ ->items([
+ (new Para())
+ ->class('col checkboxes-helpers'),
+ (new Para())
+ ->class('col right')
+ ->items(array_merge(
+ dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), $feeds_filter->values(true)),
+ [
+ (new Label(__('Selected feeds action:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('action'),
+ (new Select('action'))
+ ->items($feeds_actions_page->getCombo()),
+ (new Submit('feeds-action'))
+ ->value(__('ok')),
+ dcCore::app()->formNonce(false),
+
+ ]
+ )),
+ ]),
+ ])
+ ->render()
+ );
}
- echo '
- ';
- }
-
- # entries
- if ($feed_id && $can_view_page && isset($post_filter) && isset($post_list) && isset($posts_actions_page) && !dcCore::app()->error->flag()) {
- echo '';
-
- # show filters
- $post_filter->display(
- ['admin.plugin.' . basename(__DIR__),'#entries'],
- dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), [
- 'part' => 'feed',
- 'feed_id' => $feed_id,
- ])
- );
-
- # fix pager url
- $args = $post_filter->values();
- unset($args['page']);
- $args['page'] = '%s';
-
- # show posts
- $post_list->display(
- $post_filter->page,
- $post_filter->nb,
- dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), $args, '&') . '#entries',
- '
',
- $post_filter->show()
- );
-
- echo '
';
- }
-
-############################################################
-#
-# All feeds
-#
-############################################################
-} else {
- # actions
- $feeds_actions_page = new zcfsFeedsActions(
- 'plugin.php',
- ['p' => basename(__DIR__), 'part' => 'feeds']
- );
- if ($feeds_actions_page->process()) {
- return null;
- }
-
- # filters
- $feeds_filter = new adminGenericFilter(dcCore::app(), 'zcfs_feeds');
- $feeds_filter->add('part', 'feeds');
- $feeds_filter->add(dcAdminFilters::getPageFilter());
- $feeds_filter->add(dcAdminFilters::getSearchFilter());
- $params = $feeds_filter->params();
-
- # feeds
- try {
- $feeds = $zcfs->getFeeds($params);
- $feeds_counter = $zcfs->getFeeds($params, true)->f(0);
- $feeds_list = new zcfsFeedsList(
- dcCore::app(),
- $feeds,
- $feeds_counter
- );
- } catch (Exception $e) {
- dcCore::app()->error->add($e->getMessage());
- }
-
- # display
- echo
- '' . __('Feeds server') . '' .
- $feeds_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feeds'], '&')) .
- dcPage::jsLoad(dcPage::getPF(basename(__DIR__) . '/js/list.js')) .
- dcPage::jsPageTabs() .
-
- # --BEHAVIOR-- adminZoneclearFeedServerHeader
- dcCore::app()->callBehavior('adminZoneclearFeedServerHeader') .
-
- '' .
-
- dcPage::breadcrumb([
- __('Plugins') => '',
- __('Feeds server') => '',
- ]) .
- dcPage::notices();
-
- if (isset($feeds_list)) {
- echo
- '' .
- '' .
- __('New feed') . '
';
-
- $feeds_filter->display(
- 'admin.plugin.' . basename(__DIR__),
- dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), ['part' => 'feeds'])
- );
-
- $feeds_list->feedsDisplay(
- $feeds_filter->page,
- $feeds_filter->nb,
- '',
- $feeds_filter->show()
- );
+ dcPage::closeModule();
}
}
-
-echo '';
diff --git a/src/ManageFeed.php b/src/ManageFeed.php
new file mode 100644
index 0000000..087a593
--- /dev/null
+++ b/src/ManageFeed.php
@@ -0,0 +1,463 @@
+auth) && !is_null(dcCore::app()->blog)
+ && dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
+ dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
+ ]), dcCore::app()->blog->id)
+ && ($_REQUEST['part'] ?? 'feeds') === 'feed';
+
+ return static::$init;
+ }
+
+ public static function process(): bool
+ {
+ if (!static::$init) {
+ return false;
+ }
+
+ // no action
+ if (empty($_POST['action'])) {
+ return true;
+ }
+
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
+ $v = ManageFeedVars::instance();
+
+ // save feed
+ if ($_POST['action'] == 'savefeed') {
+ // check values
+ $testfeed_params['feed_feed'] = $v->feed;
+ if ($v->id) {
+ $testfeed_params['sql'] = 'AND feed_id <> ' . $v->id . ' ';
+ }
+ if ($z->getFeeds($testfeed_params, true)->f(0)) {
+ dcCore::app()->error->add(__('Record with same feed URL already exists.'));
+ }
+ if (empty($v->name)) {
+ dcCore::app()->error->add(__('You must provide a name.'));
+ }
+ if (empty($v->owner)) {
+ dcCore::app()->error->add(__('You must provide an owner.'));
+ }
+ if (!$z::validateURL($v->url)) {
+ dcCore::app()->error->add(__('You must provide valid site URL.'));
+ }
+ if (!$z::validateURL($v->feed)) {
+ dcCore::app()->error->add(__('You must provide valid feed URL.'));
+ }
+ if (null !== $v->cat_id && !dcCore::app()->blog?->getCategory($v->cat_id)) {
+ dcCore::app()->error->add(__('You must provide valid category.'));
+ }
+
+ // check failed
+ if (dcCore::app()->error->flag()) {
+ return true;
+ }
+
+ // save feed
+ try {
+ $id = $v->save();
+ if (!$id) {
+ throw new Exception(__('Failed to save feed.'));
+ }
+
+ dcPage::addSuccessNotice(
+ __('Feed successfully created.')
+ );
+ dcCore::app()->adminurl?->redirect(
+ 'admin.plugin.' . My::id(),
+ ['part' => 'feed', 'feed_id' => $id]
+ );
+ } catch (Exception $e) {
+ dcCore::app()->error->add($e->getMessage());
+
+ return true;
+ }
+
+ return true;
+ }
+
+ return true;
+ }
+
+ public static function render(): void
+ {
+ if (!static::$init) {
+ return;
+ }
+
+ if (is_null(dcCore::app()->adminurl)) {
+ return;
+ }
+
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
+ $v = ManageFeedVars::instance();
+
+ // Prepared entries list
+ if ($v->id && $v->can_view_page) {
+ // posts actions
+ $posts_actions_page = new dcPostsActions(
+ 'plugin.php',
+ [
+ 'p' => My::id(),
+ 'part' => 'feed',
+ 'feed_id' => $v->id,
+ '_ANCHOR' => 'entries',
+ ]
+ );
+ if ($posts_actions_page->process()) {
+ return;
+ }
+
+ // posts filters
+ $post_filter = new PostsFilter();
+ $post_filter->add('part', 'feed');
+ $post_filter->add('feed_id', $v->id);
+ $params = $post_filter->params();
+
+ // typehint
+ $sortby = is_string($post_filter->value('sortby')) ? $post_filter->value('sortby') : 'post_creadt';
+ $order = is_string($post_filter->value('order')) ? $post_filter->value('order') : 'DESC';
+
+ # lexical sort
+ $sortby_lex = [
+ // key in sorty_combo (see above) => field in SQL request
+ 'post_title' => 'post_title',
+ 'cat_title' => 'cat_title',
+ 'user_id' => 'P.user_id', ];
+
+ # --BEHAVIOR-- adminPostsSortbyLexCombo
+ dcCore::app()->callBehavior('adminPostsSortbyLexCombo', [& $sortby_lex]);
+
+ $params['no_content'] = true;
+ $params['feed_id'] = $v->id;
+ $params['order'] = (
+ array_key_exists($sortby, $sortby_lex) ?
+ dcCore::app()->con->lexFields($sortby_lex[$sortby]) :
+ $sortby
+ ) . ' ' . $order;
+
+ # posts
+ try {
+ $posts = $z->getPostsByFeed($params);
+ $counter = $z->getPostsByFeed($params, true);
+ $post_list = new PostsList($posts, $counter->f(0));
+ } catch (Exception $e) {
+ dcCore::app()->error->add($e->getMessage());
+ }
+ }
+
+ // display
+ dcPage::openModule(
+ My::id(),
+ (
+ $v->id && isset($post_filter) && !dcCore::app()->error->flag() ?
+ $post_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed', 'feed_id' => $v->id], '&') . '#entries') .
+ dcPage::jsModuleLoad(My::id() . '/js/list.js')
+ : ''
+ ) .
+ dcPage::jsPageTabs() .
+ $v->next_headlink . "\n" . $v->prev_headlink
+ );
+
+ echo
+ dcPage::breadcrumb([
+ __('Plugins') => '',
+ My::name() => dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
+ ($v->id ? __('Edit feed') : __('New feed')) => '',
+ ]) .
+ dcPage::notices() .
+ (new Text('h3', ($v->id ? sprintf(__('Edit feed "%s"'), Html::escapeHTML($v->name)) : __('New feed'))))->render();
+
+ if ($v->can_view_page) {
+ # nav link
+ if ($v->id && ($v->next_link || $v->prev_link)) {
+ $text = '';
+ if ($v->prev_link) {
+ $text .= $v->prev_link;
+ }
+ if ($v->next_link && $v->prev_link) {
+ $text .= ' | ';
+ }
+ if ($v->next_link) {
+ $text .= $v->next_link;
+ }
+ echo (new Text('p', $text))->class('nav_prevnext')->render();
+ }
+
+ echo
+ (new Div('edit-entry'))
+ ->class($v->id ? 'multi-part' : '')
+ ->title($v->id ? __('Feed') : '')
+ ->items([
+ (new Form('edit-entry-form'))
+ ->method('post')
+ ->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id()))
+ ->fields([
+ (new Div())
+ ->class('two-cols')
+ ->items([
+ (new Div())
+ ->class('col70')
+ ->items([
+ (new Text('h4', __('Feed information'))),
+ // feed_name
+ (new Para())
+ ->items([
+ (new Label(__('Name:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->class('required')
+ ->for('feed_name'),
+ (new Input('feed_name'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->name),
+ ]),
+ // feed_owner
+ (new Para())
+ ->items([
+ (new Label(__('Owner:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->class('required')
+ ->for('feed_owner'),
+ (new Input('feed_owner'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->owner),
+ ]),
+ // feed_url
+ (new Para())
+ ->items([
+ (new Label(__('Site URL:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->class('required')
+ ->for('feed_url'),
+ (new Input('feed_url'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->url),
+ ]),
+ // feed_feed
+ (new Para())
+ ->items([
+ (new Label(__('Feed URL:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->class('required')
+ ->for('feed_feed'),
+ (new Input('feed_feed'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->feed),
+ ]),
+ // feed_desc
+ (new Para())
+ ->items([
+ (new Label(__('Description:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_desc'),
+ (new Input('feed_desc'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->desc),
+ ]),
+ // feed_tags
+ (new Para())
+ ->items([
+ (new Label(__('Tags:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_tags'),
+ (new Input('feed_tags'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->tags),
+ ]),
+ // feed_tweeter
+ (new Para())
+ ->items([
+ (new Label(__('Tweeter or Identica ident:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_tweeter'),
+ (new Input('feed_tweeter'))
+ ->class('maximal')
+ ->size(60)
+ ->maxlenght(255)
+ ->value($v->tweeter),
+ ]),
+ ]),
+ (new Div())
+ ->class('col30')
+ ->items([
+ (new Text('h4', __('Local settings'))),
+ // feed_cat_id
+ (new Para())
+ ->items([
+ (new Label(__('Category:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_cat_id'),
+ (new Select('feed_cat_id'))
+ ->class('maximal')
+ ->items(Combo::postCategories())
+ ->default((string) $v->cat_id),
+ ]),
+ // feed_status
+ (new Para())
+ ->items([
+ (new Label(__('Status:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_status'),
+ (new Select('feed_status'))
+ ->class('maximal')
+ ->items(Combo::feedsStatus())
+ ->default((string) $v->status),
+ ]),
+ // feed_upd_int
+ (new Para())
+ ->items([
+ (new Label(__('Update:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_upd_int'),
+ (new Select('feed_upd_int'))
+ ->class('maximal')
+ ->items(Combo::updateInterval())
+ ->default((string) $v->upd_int),
+ ]),
+ // feed_lang
+ (new Para())
+ ->items([
+ (new Label(__('Lang:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('feed_lang'),
+ (new Select('feed_lang'))
+ ->class('maximal')
+ ->items(L10n::getISOcodes(true))
+ ->default((string) $v->lang),
+ ]),
+ // feed_get_tags
+ (new Para())->items([
+ (new Checkbox('feed_get_tags', $v->get_tags))
+ ->value(1),
+ (new Label(__('Import tags from feed'), Label::OUTSIDE_LABEL_AFTER))
+ ->class('classic')
+ ->for('feed_get_tags'),
+ ]),
+ ]),
+ ]),
+ (new Para())
+ ->class('clear')
+ ->items(array_merge(
+ dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), [
+ 'part' => 'feed',
+ 'feed_id' => $v->id,
+ 'action' => 'savefeed',
+ ]),
+ [
+ (new Submit(['save']))
+ ->value(__('Save') . ' (s)')
+ ->accesskey('s'),
+ dcCore::app()->formNonce(false),
+ ]
+ )),
+ ]),
+ ])
+ ->render();
+ }
+
+ if ($v->id && $v->can_view_page && isset($post_filter) && isset($post_list) && isset($posts_actions_page) && !dcCore::app()->error->flag()) {
+ echo '';
+
+ # show posts filters
+ $post_filter->display(
+ ['admin.plugin.' . My::id(),'#entries'],
+ dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . My::id(), [
+ 'part' => 'feed',
+ 'feed_id' => $v->id,
+ ])
+ );
+
+ # fix pager url
+ $args = $post_filter->values();
+ unset($args['page']);
+ $args['page'] = '%s';
+
+ # show posts
+ $post_list->display(
+ $post_filter,
+ dcCore::app()->adminurl->get('admin.plugin.' . My::id(), $args, '&') . '#entries',
+ (new Form('form-entries'))
+ ->method('post')
+ ->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed']) . '#entries')
+ ->fields([
+ (new Text('', '%s')),
+ (new Div())
+ ->class('two-cols')
+ ->items([
+ (new Para())
+ ->class('col checkboxes-helpers'),
+ (new Para())
+ ->class('col right')
+ ->items(array_merge(
+ dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), $post_filter->values()),
+ [
+ (new Hidden('redir', dcCore::app()->adminurl->get('admin.plugin.' . My::id(), $post_filter->values()))),
+ (new Label(__('Selected entries action:'), Label::OUTSIDE_LABEL_BEFORE))
+ ->for('action'),
+ (new Select('action'))
+ ->items($posts_actions_page->getCombo()),
+ (new Submit('feed-action'))
+ ->value(__('ok')),
+ dcCore::app()->formNonce(false),
+
+ ]
+ )),
+
+ ]),
+ ])
+ ->render()
+ );
+
+ echo '
';
+ }
+
+ dcPage::closeModule();
+ }
+}
diff --git a/src/ManageFeedVars.php b/src/ManageFeedVars.php
new file mode 100644
index 0000000..4f7c1e2
--- /dev/null
+++ b/src/ManageFeedVars.php
@@ -0,0 +1,242 @@
+admin->getPageURL() . '&part=feed&feed_id=%s" />';
+ $feed_link = '%s';
+ $lang = dcCore::app()->auth?->getInfo('user_lang');
+
+ // default values
+ $feed_id = 0;
+ $feed_name = '';
+ $feed_desc = '';
+ $feed_owner = '';
+ $feed_tweeter = '';
+ $feed_url = '';
+ $feed_feed = '';
+ $feed_lang = is_string($lang) ? $lang : 'en';
+ $feed_tags = '';
+ $feed_get_tags = false;
+ $feed_cat_id = null;
+ $feed_status = 0;
+ $feed_upd_int = 86400;
+
+ $can_view_page = true;
+ $next_link = '';
+ $prev_link = '';
+ $next_headlink = '';
+ $prev_headlink = '';
+
+ // database values
+ if (!empty($_REQUEST['feed_id'])) {
+ $feed = $z->getFeeds(['feed_id' => $_REQUEST['feed_id']]);
+
+ if ($feed->isEmpty()) {
+ dcCore::app()->error->add(__('This feed does not exist.'));
+ $can_view_page = false;
+ } else {
+ $row = new FeedRow($feed);
+ $feed_id = $row->id;
+ $feed_name = $row->name;
+ $feed_desc = $row->desc;
+ $feed_owner = $row->owner;
+ $feed_tweeter = $row->tweeter;
+ $feed_url = $row->url;
+ $feed_feed = $row->feed;
+ $feed_lang = $row->lang;
+ $feed_tags = $row->tags;
+ $feed_get_tags = $row->get_tags;
+ $feed_cat_id = $row->cat_id;
+ $feed_status = $row->status;
+ $feed_upd_int = $row->upd_int;
+
+ $next_params = [
+ 'sql' => 'AND feed_id < ' . $feed_id . ' ',
+ 'limit' => 1,
+ ];
+ $next_rs = $z->getFeeds($next_params);
+
+ if (!$next_rs->isEmpty()) {
+ $next_row = new FeedRow($next_rs);
+ $next_link = sprintf(
+ $feed_link,
+ $next_row->id,
+ Html::escapeHTML($next_row->name),
+ __('next feed') . ' »'
+ );
+ $next_headlink = sprintf(
+ $feed_headlink,
+ 'next',
+ Html::escapeHTML($next_row->name),
+ $next_row->id
+ );
+ }
+
+ $prev_params = [
+ 'sql' => 'AND feed_id > ' . $feed_id . ' ',
+ 'limit' => 1,
+ ];
+ $prev_rs = $z->getFeeds($prev_params);
+
+ if (!$prev_rs->isEmpty()) {
+ $prev_row = new FeedRow($prev_rs);
+ $prev_link = sprintf(
+ $feed_link,
+ $prev_row->id,
+ Html::escapeHTML($prev_row->name),
+ '« ' . __('previous feed')
+ );
+ $prev_headlink = sprintf(
+ $feed_headlink,
+ 'previous',
+ Html::escapeHTML($prev_row->name),
+ $prev_row->id
+ );
+ }
+ }
+ }
+
+ // form values
+ if (!empty($_POST)) {
+ $feed_name = !empty($_POST['feed_name']) && is_string($_POST['feed_name']) ? $_POST['feed_name'] : $feed_name;
+ $feed_desc = !empty($_POST['feed_desc']) && is_string($_POST['feed_desc']) ? $_POST['feed_desc'] : $feed_desc;
+ $feed_owner = !empty($_POST['feed_owner']) && is_string($_POST['feed_owner']) ? $_POST['feed_owner'] : $feed_owner;
+ $feed_tweeter = !empty($_POST['feed_tweeter']) && is_string($_POST['feed_tweeter']) ? $_POST['feed_tweeter'] : $feed_tweeter;
+ $feed_url = !empty($_POST['feed_url']) && is_string($_POST['feed_url']) ? $_POST['feed_url'] : $feed_url;
+ $feed_feed = !empty($_POST['feed_feed']) && is_string($_POST['feed_feed']) ? $_POST['feed_feed'] : $feed_feed;
+ $feed_lang = !empty($_POST['feed_lang']) && is_string($_POST['feed_lang']) ? $_POST['feed_lang'] : $feed_lang;
+ $feed_tags = !empty($_POST['feed_tags']) && is_string($_POST['feed_tags']) ? $_POST['feed_tags'] : $feed_tags;
+ $feed_get_tags = empty($_POST['feed_get_tags']) ? $feed_get_tags : true;
+ $feed_cat_id = empty($_POST['feed_cat_id']) ? $feed_cat_id : $_POST['feed_cat_id'];
+ $feed_upd_int = !empty($_POST['feed_upd_int']) && is_numeric($_POST['feed_upd_int']) ? (int) $_POST['feed_upd_int'] : $feed_upd_int;
+ $feed_status = empty($_POST['feed_status']) ? $feed_status : 1;
+ }
+
+ // class values
+ $this->id = $feed_id;
+ $this->name = $feed_name;
+ $this->desc = $feed_desc;
+ $this->owner = $feed_owner;
+ $this->tweeter = $feed_tweeter;
+ $this->url = $feed_url;
+ $this->feed = $feed_feed;
+ $this->lang = $feed_lang;
+ $this->tags = $feed_tags;
+ $this->get_tags = $feed_get_tags;
+ $this->cat_id = $feed_cat_id;
+ $this->status = $feed_status;
+ $this->upd_int = $feed_upd_int;
+
+ $this->can_view_page = $can_view_page;
+ $this->next_link = $next_link;
+ $this->prev_link = $prev_link;
+ $this->next_headlink = $next_headlink;
+ $this->prev_headlink = $prev_headlink;
+ }
+
+ /**
+ * Get self instance.
+ *
+ * @return ManageFeedVars Self instance
+ */
+ public static function instance(): ManageFeedVars
+ {
+ if (!(self::$container instanceof self)) {
+ self::$container = new self();
+ }
+
+ return self::$container;
+ }
+
+ /**
+ * Create or update feed.
+ *
+ * @return int The feed ID
+ */
+ public function save()
+ {
+ $z = ZoneclearFeedServer::instance();
+ $id = $this->id;
+
+ // prepare cursor
+ $cur = $z->openCursor();
+ $cur->setField('feed_name', $this->name);
+ $cur->setField('feed_desc', $this->desc);
+ $cur->setField('feed_owner', $this->owner);
+ $cur->setField('feed_tweeter', $this->tweeter);
+ $cur->setField('feed_url', $this->url);
+ $cur->setField('feed_feed', $this->feed);
+ $cur->setField('feed_lang', $this->lang);
+ $cur->setField('feed_tags', $this->tags);
+ $cur->setField('feed_get_tags', $this->get_tags);
+ $cur->setField('cat_id', $this->cat_id);
+ $cur->setField('feed_status', $this->status);
+ $cur->setField('feed_upd_int', $this->upd_int);
+
+ # --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedSave - Cursor, int
+ dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedSave', $cur, $id);
+
+ if (!$id) {
+ // create feed
+ $id = $z->addFeed($cur);
+ } else {
+ // update feed
+ $z->updateFeed($id, $cur);
+ }
+
+ # --BEHAVIOR-- adminAfterZoneclearFeedServerFeedSave - Cursor - int
+ dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedSave', $cur, $id);
+
+ return $id;
+ }
+}
diff --git a/src/My.php b/src/My.php
new file mode 100644
index 0000000..d24c0e8
--- /dev/null
+++ b/src/My.php
@@ -0,0 +1,94 @@
+ This module template blocks */
+ public const TPL_BLOCKS = [
+ 'Feeds',
+ 'FeedsFooter',
+ 'FeedsHeader',
+ 'FeedIf',
+ ];
+
+ /** @var array This module template values */
+ public const TPL_VALUES = [
+ 'FeedsCount',
+ 'FeedsEntriesCount',
+ 'FeedEntriesCount',
+ 'FeedCategory',
+ 'FeedCategoryID',
+ 'FeedCategoryURL',
+ 'FeedCategoryShortURL',
+ 'FeedID',
+ 'FeedIfFirst',
+ 'FeedIfOdd',
+ 'FeedLang',
+ 'FeedName',
+ 'FeedOwner',
+ 'FeedDesc',
+ 'FeedSiteURL',
+ 'FeedFeedURL',
+ ];
+
+ /**
+ * @return string This module id
+ */
+ public static function id(): string
+ {
+ return basename(dirname(__DIR__));
+ }
+
+ /**
+ * @return string This module name
+ */
+ public static function name(): string
+ {
+ $name = dcCore::app()->plugins->moduleInfo(self::id(), 'name');
+
+ return __(is_string($name) ? $name : self::id());
+ }
+
+ /**
+ * @return string This module path
+ */
+ public static function path(): string
+ {
+ return dirname(__DIR__);
+ }
+
+ /**
+ * @return bool True on this module php version complied
+ */
+ public static function phpCompliant(): bool
+ {
+ return version_compare(phpversion(), self::PHP_MIN, '>=');
+ }
+}
diff --git a/src/PostsFilter.php b/src/PostsFilter.php
index 0aac214..867db04 100644
--- a/src/PostsFilter.php
+++ b/src/PostsFilter.php
@@ -10,23 +10,31 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use adminGenericFilterV2;
+use dcAdminCombos;
+use dcAdminFilter;
+use dcAdminFilters;
+use dcCore;
+use dcUtils;
+use Dotclear\Helper\Html\Html;
+use Exception;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Feeds server - Posts list filters methods
- * @since 2.20
- * @see adminGenericFilter for more info
+ * Backend feed posts list filters.
*/
-class zcfsPostFilter extends adminGenericFilter
+class PostsFilter extends adminGenericFilterV2
{
public function __construct()
{
- parent::__construct(dcCore::app(), 'zcfs_entries');
+ // use user posts pref
+ parent::__construct('posts');
- $filters = new arrayObject([
+ $filters = new ArrayObject([
dcAdminFilters::getPageFilter(),
$this->getPostUserFilter(),
$this->getPostCategoriesFilter(),
@@ -50,8 +58,8 @@ class zcfsPostFilter extends adminGenericFilter
$users = null;
try {
- $users = dcCore::app()->blog->getPostsUsers();
- if ($users->isEmpty()) {
+ $users = dcCore::app()->blog?->getPostsUsers();
+ if (is_null($users) || $users->isEmpty()) {
return null;
}
} catch (Exception $e) {
@@ -81,8 +89,8 @@ class zcfsPostFilter extends adminGenericFilter
$categories = null;
try {
- $categories = dcCore::app()->blog->getCategories();
- if ($categories->isEmpty()) {
+ $categories = dcCore::app()->blog?->getCategories();
+ if (is_null($categories) || $categories->isEmpty()) {
return null;
}
} catch (Exception $e) {
@@ -96,10 +104,12 @@ class zcfsPostFilter extends adminGenericFilter
__('(No cat)') => 'NULL',
];
while ($categories->fetch()) {
- $combo[
- str_repeat(' ', ($categories->level - 1) * 4) .
- html::escapeHTML($categories->cat_title) . ' (' . $categories->nb_post . ')'
- ] = $categories->cat_id;
+ if (is_numeric($categories->f('level')) && is_string($categories->f('cat_title'))) {
+ $combo[
+ str_repeat(' ', ((int) $categories->f('level') - 1) * 4) .
+ Html::escapeHTML($categories->f('cat_title')) . ' (' . $categories->f('nb_post') . ')'
+ ] = $categories->f('cat_id');
+ }
}
return (new dcAdminFilter('cat_id'))
@@ -131,8 +141,8 @@ class zcfsPostFilter extends adminGenericFilter
$dates = null;
try {
- $dates = dcCore::app()->blog->getDates(['type' => 'month']);
- if ($dates->isEmpty()) {
+ $dates = dcCore::app()->blog?->getDates(['type' => 'month']);
+ if (is_null($dates) || $dates->isEmpty()) {
return null;
}
} catch (Exception $e) {
diff --git a/src/PostsList.php b/src/PostsList.php
index 615b61f..4b67912 100644
--- a/src/PostsList.php
+++ b/src/PostsList.php
@@ -10,139 +10,173 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use adminGenericListV2;
+use dcCore;
+use dcPager;
+use Dotclear\Helper\Date;
+use Dotclear\Helper\Html\Form\{
+ Checkbox,
+ Div,
+ Link,
+ Para,
+ Text
+};
+use Dotclear\Helper\Html\Html;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Feeds server - Posts list methods
- * @since 2.6
- * @see adminGenericList for more info
+ * Backend feed posts lists.
*/
-class zcfsEntriesList extends adminGenericList
+class PostsList extends adminGenericListV2
{
- public function display($page, $nb_per_page, $base_url, $enclose_block = '', $filter = false)
+ public function display(PostsFilter $filter, string $base_url, string $enclose_block = ''): void
{
if ($this->rs->isEmpty()) {
- echo '' . (
- $filter ?
+ echo
+ (new Text(
+ 'p',
+ $filter->show() ?
__('No entries matches the filter') :
__('No entries')
- ) . '
';
- } else {
- $pager = new dcPager($page, $this->rs_count, $nb_per_page, 10);
- $pager->base_url = $base_url;
+ ))
+ ->class('info')
+ ->render();
- $entries = [];
- if (isset($_REQUEST['feeds'])) {
- foreach ($_REQUEST['feeds'] as $v) {
- $entries[(int) $v] = true;
- }
- }
-
- $html_block = '' .
- '
' .
- '' . (
- $filter ?
- sprintf(__('List of %s entries matching the filter.'), $this->rs_count) :
- sprintf(__('List of entries (%s)'), $this->rs_count)
- ) . '';
-
- $cols = [
- 'title' => '' . __('Title') . ' | ',
- 'date' => '' . __('Date') . ' | ',
- 'author' => '' . __('Author') . ' | ',
- 'category' => '' . __('Category') . ' | ',
- 'status' => '' . __('Status') . ' | ',
- ];
-
- $cols = new ArrayObject($cols);
- dcCore::app()->callBehavior('adminZcfsPostListHeader', $this->rs, $cols);
-
- $this->userColumns('zcfs_entries', $cols);
-
- $html_block .= '' . implode(iterator_to_array($cols)) . '
%s
';
- if ($enclose_block) {
- $html_block = sprintf($enclose_block, $html_block);
- }
-
- echo $pager->getLinks();
-
- $blocks = explode('%s', $html_block);
-
- echo $blocks[0];
-
- while ($this->rs->fetch()) {
- echo $this->postLine();
- }
-
- echo $blocks[1];
-
- echo $pager->getLinks();
+ return;
}
+
+ $page = is_numeric($filter->value('page')) ? (int) $filter->value('page') : 1;
+ $nbpp = is_numeric($filter->value('nb')) ? (int) $filter->value('nb') : 10;
+ $count = (int) $this->rs_count;
+ $pager = new dcPager($page, $count, $nbpp, 10);
+ $pager->base_url = $base_url;
+
+ $cols = new ArrayObject([
+ 'title' => (new Text('th', __('Title')))
+ ->class('first')
+ ->extra('colspan="2"'),
+ 'date' => (new Text('th', __('Date')))
+ ->extra('scope="col"'),
+ 'author' => (new Text('th', __('Author')))
+ ->extra('scope="col"'),
+ 'category' => (new Text('th', __('Category')))
+ ->extra('scope="col"'),
+ 'status' => (new Text('th', __('Status')))
+ ->extra('scope="col"'),
+ ]);
+
+ $this->userColumns(My::id() . 'posts', $cols);
+
+ $lines = [];
+ while ($this->rs->fetch()) {
+ $lines[] = $this->line(isset($_POST['entries']) && in_array($this->rs->post_id, $_POST['entries']));
+ }
+
+ echo
+ $pager->getLinks() .
+ sprintf(
+ $enclose_block,
+ (new Div())
+ ->class('table-outer')
+ ->items([
+ (new Para(null, 'table'))
+ ->items([
+ (new Text(
+ 'caption',
+ $filter->show() ?
+ sprintf(__('List of %s entries matching the filter.'), $this->rs_count) :
+ sprintf(__('List of entries. (%s)'), $this->rs_count)
+ )),
+ (new Para(null, 'tr'))
+ ->items(iterator_to_array($cols)),
+ (new Para(null, 'tbody'))
+ ->items($lines),
+ ]),
+ ])
+ ->render()
+ ) .
+ $pager->getLinks();
}
- private function postLine()
+ private function line(bool $checked): Para
{
- $cat_link = dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([dcAuth::PERMISSION_CATEGORIES]), dcCore::app()->blog->id) ?
- '%s'
- : '%2$s';
+ $cat_title = (new Text('', __('None')));
+ if ($this->rs->cat_title
+ && dcCore::app()->auth?->check(dcCore::app()->auth->makePermissions([dcCore::app()->auth::PERMISSION_CATEGORIES]), dcCore::app()->blog?->id)
+ ) {
+ $cat_title = (new Link())
+ ->href('category.php?id=' . $this->rs->cat_id)
+ ->title(Html::escapeHTML(__('Edit category')))
+ ->text(Html::escapeHTML($this->rs->cat_title));
+ }
- $cat_title = $this->rs->cat_title ?
- sprintf($cat_link, $this->rs->cat_id, html::escapeHTML($this->rs->cat_title))
- : __('None');
-
- $img = '';
- $img_status = '';
- $sts_class = '';
switch ($this->rs->post_status) {
case 1:
- $img_status = sprintf($img, __('Published'), 'check-on.png');
- $sts_class = 'sts-online';
-
- break;
- case 0:
- $img_status = sprintf($img, __('Unpublished'), 'check-off.png');
- $sts_class = 'sts-offline';
+ $img_title = __('Published');
+ $img_src = 'check-on.png';
+ $sts_class = ' sts-online';
break;
case -1:
- $img_status = sprintf($img, __('Scheduled'), 'scheduled.png');
- $sts_class = 'sts-scheduled';
+ $img_title = __('Scheduled');
+ $img_src = 'scheduled.png';
+ $sts_class = ' sts-scheduled';
break;
case -2:
- $img_status = sprintf($img, __('Pending'), 'check-wrn.png');
- $sts_class = 'sts-pending';
+ $img_title = __('Pending');
+ $img_src = 'check-wrn.png';
+ $sts_class = ' sts-pending';
+
+ break;
+ default:
+ $img_title = __('Unpublished');
+ $img_src = 'check-off.png';
+ $sts_class = ' sts-offline';
break;
}
- $res = '';
+ $cols = new ArrayObject([
+ 'check' => (new Para(null, 'td'))
+ ->class('nowrap minimal')
+ ->items([
+ (new Checkbox(['entries[]'], $checked))
+ ->value($this->rs->post_id)
+ ->disabled(!$this->rs->isEditable()),
+ ]),
+ 'title' => (new Para(null, 'td'))
+ ->class('maximal')
+ ->items([
+ (new Link())
+ ->href(dcCore::app()->getPostAdminURL($this->rs->post_type, $this->rs->post_id))
+ ->title(Html::escapeHTML($this->rs->getURL()))
+ ->text(Html::escapeHTML(trim(Html::clean($this->rs->post_title)))),
+ ]),
+ 'date' => (new Text('td', Date::dt2str(__('%Y-%m-%d %H:%M'), $this->rs->post_dt)))
+ ->class('nowrap count'),
+ 'author' => (new Text('td', Html::escapeHTML($this->rs->user_id)))
+ ->class('nowrap'),
+ 'category' => (new Para(null, 'td'))
+ ->class('nowrap')
+ ->items([$cat_title]),
+ 'status' => (new Para(null, 'td'))
+ ->class('nowrap status')
+ ->items([
+ (new Text('img', ''))
+ ->title($img_title)
+ ->extra('src="images/' . $img_src . '"'),
+ ]),
+ ]);
- $cols = [
- 'check' => '' .
- form::checkbox(['entries[]'], $this->rs->post_id, '', '', '', !$this->rs->isEditable()) . ' | ',
- 'title' => 'rs->getURL()) . '">' .
- html::escapeHTML(trim(html::clean($this->rs->post_title))) . ' | ',
- 'date' => '' . dt::dt2str(__('%Y-%m-%d %H:%M'), $this->rs->post_dt) . ' | ',
- 'author' => '' . html::escapeHTML($this->rs->user_id) . ' | ',
- 'category' => '' . $cat_title . ' | ',
- 'status' => '' . $img_status . ' | ',
- ];
+ $this->userColumns(My::id() . 'posts', $cols);
- $cols = new ArrayObject($cols);
- dcCore::app()->callBehavior('adminZcfsPostListValue', $this->rs, $cols);
-
- $this->userColumns('zcfs_entries', $cols);
-
- $res .= implode(iterator_to_array($cols));
- $res .= '
';
-
- return $res;
+ return (new Para('p' . $this->rs->post_id, 'tr'))
+ ->class('line' . ($this->rs->post_status != 1 ? ' offline ' : '') . $sts_class)
+ ->items(iterator_to_array($cols));
}
}
diff --git a/src/Prepend.php b/src/Prepend.php
index 361c1e0..fad397d 100644
--- a/src/Prepend.php
+++ b/src/Prepend.php
@@ -10,35 +10,44 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
-
-Clearbricks::lib()->autoload([
- 'zoneclearFeedServer' => __DIR__ . '/inc/class.zoneclearfeedserver.php',
- 'zcfsAdminBehaviors' => __DIR__ . '/inc/class.zcfsadminbehaviors.php',
- 'zcfsPostFilter' => __DIR__ . '/inc/class.zcfspostfilter.php',
- 'zcfsEntriesList' => __DIR__ . '/inc/class.zcfsentrieslist.php',
- 'zcfsFeedsList' => __DIR__ . '/inc/class.zcfsfeedslist.php',
- 'zcfsFeedsActions' => __DIR__ . '/inc/class.zcfsfeedsactions.php',
- 'zcfsDefaultFeedsActions' => __DIR__ . '/inc/class.zcfsdefaultfeedsactions.php',
- 'zcfsTemplate' => __DIR__ . '/inc/class.zcfstemplate.php',
- 'zcfsPublicBehaviors' => __DIR__ . '/inc/class.zcfspublicbehaviors.php',
- 'zcfsRsExtPosts' => __DIR__ . '/inc/class.zcfsrsextposts.php',
- 'zcfsUrlHandler' => __DIR__ . '/inc/class.zcfsurlhandler.php',
- 'zcfsActivityReportBehaviors' => __DIR__ . '/inc/class.zcfsactivityreportbehaviors.php',
- 'zcfsUpgrade' => __DIR__ . '/inc/class.zcfsupgrade.php',
-]);
-
-// public url for page of description of the flux
-dcCore::app()->url->register(
- 'zoneclearFeedsPage',
- 'zcfeeds',
- '^zcfeeds(.*?)$',
- ['zcfsUrlHandler', 'zcFeedsPage']
-);
-
-// Add to report on plugin activityReport
-if (defined('ACTIVITY_REPORT_V2')) {
- zcfsActivityReportBehaviors::init();
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use dcCore;
+use dcNsProcess;
+
+/**
+ * Module prepend.
+ */
+class Prepend extends dcNsProcess
+{
+ public static function init(): bool
+ {
+ static::$init = My::phpCompliant();
+
+ return static::$init;
+ }
+
+ public static function process(): bool
+ {
+ if (!static::$init) {
+ return false;
+ }
+
+ // public url for page of description of the flux
+ dcCore::app()->url->register(
+ 'zoneclearFeedsPage',
+ 'zcfeeds',
+ '^zcfeeds(.*?)$',
+ [UrlHandler::class, 'zoneclearFeedsPage']
+ );
+
+ // report zoneclearFeedServer activities
+ if (defined('ACTIVITY_REPORT') && ACTIVITY_REPORT == 3) {
+ ActivityReportActions::init();
+ }
+
+ return true;
+ }
}
diff --git a/src/RsExtPosts.php b/src/RsExtPosts.php
index 3b34626..f6b7945 100644
--- a/src/RsExtPosts.php
+++ b/src/RsExtPosts.php
@@ -10,74 +10,83 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use context;
+use dcCore;
+use rsExtPost;
+use rsExtPostPublic;
+use Dotclear\Database\MetaRecord;
+use Dotclear\Helper\Html\Html;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Mix your blog with a feeds planet - rs methods.
- * @since 2.6
+ * Posts record extension to integrate feed info.
*/
-class zcfsRsExtPosts extends rsExtPost
+class RsExtPosts extends rsExtPost
{
- public static function zc()
- {
- return new zoneclearFeedServer();
- }
+ /** @var array $brother_extensions Stack posts record extensions */
+ public static array $brother_extensions = [];
/**
* Get feed meta.
*
- * @param dcRecord $rs record instance
- * @param string $info Feed info key
- * @return string Feed info value
+ * @param MetaRecord $rs The record instance
+ * @param string $info The feed info key
+ *
+ * @return null|string Feed info value
*/
- public static function zcFeed($rs, $info)
+ public static function zcFeed(MetaRecord $rs, string $info): ?string
{
$meta = dcCore::app()->meta->getMetadata([
- 'post_id' => $rs->post_id,
- 'meta_type' => 'zoneclearfeed_' . $info,
+ 'post_id' => $rs->f('post_id'),
+ 'meta_type' => My::META_PREFIX . $info,
'limit' => 1,
]);
- return $meta->isEmpty() ? null : $meta->meta_id;
+ return $meta->isEmpty() || !is_string($meta->f('meta_id')) ? null : $meta->f('meta_id');
}
/**
* Call other rs extension.
*
- * @param string $type Type of extension
- * @param array $args Arguments
- * @return mixed record extension ressource
+ * @param string $type The type of extension
+ * @param array $args The arguments
+ *
+ * @return string The record extension ressource result
*/
- public static function zcFeedBrother($type, $args)
+ public static function zcFeedBrother(string $type, array $args): string
{
- $ext = dcCore::app()->__get('beforeZcFeedRsExt');
- if (null !== $ext && !empty($ext[$type])) {
+ $ext = static::$brother_extensions;
+ if (isset($ext[$type]) && is_callable($ext[$type])) {
$func = $ext[$type];
- } elseif (is_callable(['rsExtPostPublic', $type])) {
- $func = ['rsExtPostPublic', $type];
+ } elseif (is_callable([rsExtPostPublic::class, $type])) {
+ $func = [rsExtPostPublic::class, $type];
+ } elseif (is_callable([rsExtPost::class, $type])) {
+ $func = [rsExtPost::class, $type];
} else {
- $func = ['rsExtPost', $type];
+ return '';
}
- return call_user_func_array($func, $args);
+ $cb = call_user_func_array($func, $args);
+
+ return is_string($cb) ? $cb : '';
}
/**
* Get author link from post to feed.
*
- * @param dcRecord $rs record instance
- * @return string Author link
+ * @param MetaRecord $rs The record instance
+ * @return string The author link
*/
- public static function getAuthorLink(dcRecord $rs): string
+ public static function getAuthorLink(MetaRecord $rs): string
{
- $author = $rs->zcFeed('author');
- $site = $rs->zcFeed('site');
- $sitename = $rs->zcFeed('sitename');
+ $author = $rs->__call('zcFeed', ['author']);
+ $site = $rs->__call('zcFeed', ['site']);
+ $sitename = $rs->__call('zcFeed', ['sitename']);
- return $author && $sitename ?
+ return is_string($author) && is_string($site) && is_string($sitename) ?
sprintf('%s (%s)', $author, $site, $sitename) :
self::zcFeedBrother('getAuthorLink', [&$rs]);
}
@@ -85,14 +94,15 @@ class zcfsRsExtPosts extends rsExtPost
/**
* Get author CN from post to feed.
*
- * @param dcRecord $rs record instance
- * @return string Author CN
+ * @param MetaRecord $rs The record instance
+ *
+ * @return string The author CN
*/
- public static function getAuthorCN(dcRecord $rs): string
+ public static function getAuthorCN(MetaRecord $rs): string
{
- $author = $rs->zcFeed('author');
+ $author = $rs->__call('zcFeed', ['author']);
- return $author ?
+ return is_string($author) ?
$author :
self::zcFeedBrother('getAuthorCN', [&$rs]);
}
@@ -100,36 +110,37 @@ class zcfsRsExtPosts extends rsExtPost
/**
* Get post link from post to feed.
*
- * @param dcRecord $rs record instance
- * @return string Post link
+ * @param MetaRecord $rs The record instance
+ *
+ * @return string The post link
*/
- public static function getURL(dcRecord $rs): string
+ public static function getURL(MetaRecord $rs): string
{
- $url = $rs->zcFeed('url');
- $types = json_decode(dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->post_title_redir);
- $full = is_array($types) && in_array(dcCore::app()->url->type, $types);
+ $url = $rs->__call('zcFeed', ['url']);
+ $site = $rs->__call('zcFeed', ['site']);
+ $full = in_array(dcCore::app()->url->type, ZoneclearFeedServer::instance()->settings->post_title_redir);
- return $url && $full ?
- zoneclearFeedServer::absoluteURL($rs->zcFeed('site'), $url) :
+ return is_string($site) && is_string($url) && $full ?
+ ZoneclearFeedServer::instance()::absoluteURL($site, $url) :
self::zcFeedBrother('getURL', [&$rs]);
}
/**
* Get post content from post to feed.
*
- * @param dcRecord $rs record instance
- * @return string Post content
+ * @param MetaRecord $rs The record instance
+ * @param mixed $absolute_urls Serve absolute URL (type "mixed" from rsExtPost)
+ *
+ * @return string The post content
*/
- public static function getContent(dcRecord $rs, bool $absolute_urls = false): string
+ public static function getContent(MetaRecord $rs, mixed $absolute_urls = false): string
{
- $url = $rs->zcFeed('url');
- $sitename = $rs->zcFeed('sitename');
+ $url = $rs->__call('zcFeed', ['url']);
+ $sitename = $rs->__call('zcFeed', ['sitename']);
$content = self::zcFeedBrother('getContent', [&$rs, $absolute_urls]);
- if ($url && $sitename && $rs->post_type == 'post') {
- $types = json_decode(dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->post_full_tpl);
-
- if (is_array($types) && in_array(dcCore::app()->url->type, $types)) {
+ if (is_string($url) && is_string($sitename) && $rs->f('post_type') == 'post') {
+ if (in_array(dcCore::app()->url->type, ZoneclearFeedServer::instance()->settings->post_full_tpl)) {
return $content . sprintf(
'%s
',
sprintf(__('Original post on %s'), $url, $sitename)
@@ -137,7 +148,7 @@ class zcfsRsExtPosts extends rsExtPost
}
$content = context::remove_html($content);
$content = context::cut_string($content, 350);
- $content = html::escapeHTML($content);
+ $content = Html::escapeHTML($content);
return sprintf(
'%s... %s
',
diff --git a/src/Settings.php b/src/Settings.php
new file mode 100644
index 0000000..fa4fa4f
--- /dev/null
+++ b/src/Settings.php
@@ -0,0 +1,125 @@
+ */
+ public readonly array $post_full_tpl;
+
+ /** @var array */
+ public readonly array $post_title_redir;
+
+ public readonly string $user;
+
+ /**
+ * Constructor set up plugin settings.
+ */
+ protected function __construct()
+ {
+ if (is_null(dcCore::app()->blog)) {
+ throw new Exception(__('Blog is not defined'));
+ }
+
+ $s = dcCore::app()->blog->settings->get(My::id());
+
+ $update_limit = is_numeric($s->get('update_limit')) ? (int) $s->get('update_limit') : 1;
+
+ $this->active = !empty($s->get('active'));
+ $this->pub_active = !empty($s->get('pub_active'));
+ $this->post_status_new = !empty($s->get('post_status_new'));
+ $this->bhv_pub_upd = is_numeric($s->get('bhv_pub_upd')) ? (int) $s->get('bhv_pub_upd') : 1;
+ $this->update_limit = $update_limit < 1 ? 10 : $update_limit;
+ $this->keep_empty_feed = !empty($s->get('keep_empty_feed'));
+ $this->tag_case = is_numeric($s->get('tag_case')) ? (int) $s->get('tag_case') : 0;
+ $this->post_full_tpl = is_array($s->get('post_full_tpl')) ? $s->get('post_full_tpl') : [];
+ $this->post_title_redir = is_array($s->get('post_title_redir')) ? $s->get('post_title_redir') : [];
+ $this->user = is_string($s->get('user')) ? $s->get('user') : '';
+ }
+
+ public static function instance(): Settings
+ {
+ if (!(self::$instance instanceof Settings)) {
+ self::$instance = new Settings();
+ }
+
+ return self::$instance;
+ }
+
+ public function isset(string $key): bool
+ {
+ return property_exists($this, $key);
+ }
+
+ public function get(string $key): mixed
+ {
+ return $this->{$key} ?? null;
+ }
+
+ /**
+ * Overwrite a plugin settings (in db).
+ *
+ * @param string $key The setting ID
+ * @param mixed $value The setting value
+ *
+ * @return bool True on success
+ */
+ public function set(string $key, mixed $value): bool
+ {
+ $s = dcCore::app()->blog?->settings->get(My::id());
+
+ if (!is_null($s) && property_exists($this, $key) && settype($value, gettype($this->{$key})) === true) {
+ $s->drop($key);
+ $s->put($key, $value, gettype($this->{$key}), '', true, true);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * List defined settings keys.
+ *
+ * @return array The settings keys
+ */
+ public function dump(): array
+ {
+ return get_object_vars($this);
+ }
+}
diff --git a/src/Template.php b/src/Template.php
index cc81e21..bf7ba01 100644
--- a/src/Template.php
+++ b/src/Template.php
@@ -10,44 +10,47 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use ArrayObject;
+use dcCore;
+use dcTemplate;
+use Dotclear\Helper\Html\Html;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Mix your blog with a feeds planet - template methods.
- * @since 2.6
+ * Frontend template blocks and values.
*/
-class zcfsTemplate
+class Template
{
- public static function Feeds($a, $c)
+ public static function Feeds(ArrayObject $a, string $c): string
{
$lastn = -1;
$p = '';
- if (isset($a['lastn'])) {
+ if (isset($a['lastn']) && is_numeric($a['lastn'])) {
$lastn = abs((int) $a['lastn']) + 0;
$p .= "\$zcfs_params['limit'] = " . $lastn . ";\n";
}
- if (isset($a['cat_id'])) {
+ if (isset($a['cat_id']) && is_string($a['cat_id'])) {
$p .= "@\$zcfs_params['sql'] .= 'AND Z.cat_id = " . addslashes($a['cat_id']) . " ';\n";
}
if (isset($a['no_category'])) {
$p .= "@\$zcfs_params['sql'] .= 'AND Z.cat_id IS NULL ';\n";
}
- if (!empty($a['site_url'])) {
+ if (!empty($a['site_url']) && is_string($a['site_url'])) {
$p .= "\$zcfs_params['feed_url'] = '" . addslashes($a['site_url']) . "';\n";
}
- if (isset($a['feed_status'])) {
+ if (isset($a['feed_status']) && is_numeric($a['feed_status'])) {
$p .= "\$zcfs_params['feed_status'] = " . ((int) $a['feed_status']) . ";\n";
} else {
$p .= "\$zcfs_params['feed_status'] = 1;\n";
}
- if (!empty($a['feed_url'])) {
+ if (!empty($a['feed_url']) && is_string($a['feed_url'])) {
$p .= "\$zcfs_params['feed_feed'] = '" . addslashes($a['feed_url']) . "';\n";
}
- if (isset($a['feed_owner'])) {
- $p .= "@\$zcfs_params['sql'] .= \"AND Z.feed_owner = '" . addslashes($a['author']) . "' \";\n";
+ if (isset($a['feed_owner']) && is_string($a['feed_owner'])) {
+ $p .= "@\$zcfs_params['sql'] .= \"AND Z.feed_owner = '" . addslashes($a['feed_owner']) . "' \";\n";
}
$sortby = 'feed_creadt';
@@ -71,7 +74,7 @@ class zcfsTemplate
break;
}
}
- if (isset($a['order']) && preg_match('/^(desc|asc)$/i', $a['order'])) {
+ if (isset($a['order']) && is_string($a['order']) && preg_match('/^(desc|asc)$/i', $a['order'])) {
$order = $a['order'];
}
$p .= "\$zcfs_params['order'] = '" . $sortby . ' ' . $order . "';\n";
@@ -79,26 +82,26 @@ class zcfsTemplate
return
'ctx->feeds_params = $zcfs_params;' . "\n" .
- '$zcfs = new zoneclearFeedServer();' . "\n" .
+ '$zcfs = ' . ZoneclearFeedServer::class . '::instance();' . "\n" .
'dcCore::app()->ctx->feeds = $zcfs->getFeeds($zcfs_params); unset($zcfs_params,$zcfs);' . "\n" .
"?>\n" .
'ctx->feeds->fetch()) : ?>' . $c . 'ctx->feeds = null; dcCore::app()->ctx->feeds_params = null; ?>';
}
- public static function FeedIf($a, $c)
+ public static function FeedIf(ArrayObject $a, string $c): string
{
$if = [];
- $operator = isset($a['operator']) ? self::getOperator($a['operator']) : '&&';
+ $operator = isset($a['operator']) && is_string($a['operator']) ? dcTemplate::getOperator($a['operator']) : '&&';
- if (isset($a['type'])) {
+ if (isset($a['type']) && is_string($a['type'])) {
$type = trim($a['type']);
$type = !empty($type) ? $type : 'feed';
$if[] = 'dcCore::app()->ctx->feeds->feed_type == "' . addslashes($type) . '"';
}
- if (isset($a['site_url'])) {
- $url = trim($a['feed_url']);
+ if (isset($a['site_url']) && is_string($a['site_url'])) {
+ $url = trim($a['site_url']);
if (substr($url, 0, 1) == '!') {
$url = substr($url, 1);
$if[] = 'dcCore::app()->ctx->feeds->feed_url != "' . addslashes($url) . '"';
@@ -106,8 +109,8 @@ class zcfsTemplate
$if[] = 'dcCore::app()->ctx->feeds->feed_url == "' . addslashes($url) . '"';
}
}
- if (isset($a['feed_url'])) {
- $url = trim($a['feed_feed']);
+ if (isset($a['feed_url']) && is_string($a['feed_url'])) {
+ $url = trim($a['feed_url']);
if (substr($url, 0, 1) == '!') {
$url = substr($url, 1);
$if[] = 'dcCore::app()->ctx->feeds->feed_feed != "' . addslashes($url) . '"';
@@ -115,7 +118,7 @@ class zcfsTemplate
$if[] = 'dcCore::app()->ctx->feeds->feed_feed == "' . addslashes($url) . '"';
}
}
- if (isset($a['category'])) {
+ if (isset($a['category']) && is_string($a['category'])) {
$category = addslashes(trim($a['category']));
if (substr($category, 0, 1) == '!') {
$category = substr($category, 1);
@@ -146,112 +149,100 @@ class zcfsTemplate
'' . $c . '';
}
- public static function FeedIfFirst($a)
+ public static function FeedIfFirst(ArrayObject $a): string
{
- $ret = $a['return'] ?? 'first';
- $ret = html::escapeHTML($ret);
+ $ret = Html::escapeHTML(isset($a['return']) && is_string($a['return']) ? $a['return'] : 'first');
return
'ctx->feeds->index() == 0) { ' .
"echo '" . addslashes($ret) . "'; } ?>";
}
- public static function FeedIfOdd($a)
+ public static function FeedIfOdd(ArrayObject $a): string
{
- $ret = $a['return'] ?? 'odd';
- $ret = html::escapeHTML($ret);
+ $ret = Html::escapeHTML(isset($a['return']) && is_string($a['return']) ? $a['return'] : 'odd');
return
'ctx->feeds->index()+1)%2 == 1) { ' .
"echo '" . addslashes($ret) . "'; } ?>";
}
- public static function FeedDesc($a)
+ public static function FeedDesc(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_desc');
}
- public static function FeedOwner($a)
+ public static function FeedOwner(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_owner');
}
- public static function FeedCategory($a)
+ public static function FeedCategory(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_title');
}
- public static function FeedCategoryID($a)
+ public static function FeedCategoryID(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_id');
}
- public static function FeedCategoryURL($a)
+ public static function FeedCategoryURL(ArrayObject $a): string
{
- return self::getValue($a, 'dcCore::app()->blog->url.dcCore::app()->url->getBase(\'category\').\'/\'.html::sanitizeURL(dcCore::app()->ctx->feeds->cat_url)');
+ return self::getValue($a, 'dcCore::app()->blog->url.dcCore::app()->url->getBase(\'category\').\'/\'.Html::sanitizeURL(dcCore::app()->ctx->feeds->cat_url)');
}
- public static function FeedCategoryShortURL($a)
+ public static function FeedCategoryShortURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_url');
}
- public static function FeedID($a)
+ public static function FeedID(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_id');
}
- public static function FeedLang($a)
+ public static function FeedLang(ArrayObject $a): string
{
- $f = dcCore::app()->tpl->getFilters($a);
-
return empty($a['full']) ?
- 'ctx->feeds->feed_lang') . '; ?>' :
- 'ctx->feeds->feed_lang])) { echo ' .
- sprintf($f, '$langs[dcCore::app()->ctx->feeds->feed_lang]') . '; } else { echo ' .
- sprintf($f, 'dcCore::app()->ctx->feeds->feed_lang') . '; } unset($langs); ?>';
+ self::getValue($a, 'dcCore::app()->ctx->feeds->feed_lang') :
+ 'ctx->feeds->feed_lang])) { ?>' .
+ self::getValue($a, '$langs[dcCore::app()->ctx->feeds->feed_lang]') .
+ '' .
+ self::getValue($a, 'dcCore::app()->ctx->feeds->feed_lang') .
+ '';
}
- public static function FeedName($a)
+ public static function FeedName(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_name');
}
- public static function FeedSiteURL($a)
+ public static function FeedSiteURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_url');
}
- public static function FeedFeedURL($a)
+ public static function FeedFeedURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_feed');
}
- public static function FeedsHeader($a, $c)
+ public static function FeedsHeader(ArrayObject $a, string $c): string
{
return 'ctx->feeds->isStart()) : ?>' . $c . '';
}
- public static function FeedsFooter($a, $c)
+ public static function FeedsFooter(ArrayObject $a, string $c): string
{
return 'ctx->feeds->isEnd()) : ?>' . $c . '';
}
- public static function FeedsCount($a)
+ public static function FeedsCount(ArrayObject $a): string
{
- $none = 'no sources';
- $one = 'one source';
- $more = '%d sources';
-
- if (isset($a['none'])) {
- $none = addslashes($a['none']);
- }
- if (isset($a['one'])) {
- $one = addslashes($a['one']);
- }
- if (isset($a['more'])) {
- $more = addslashes($a['more']);
- }
+ $none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no sources';
+ $one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one source';
+ $more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d sources';
return
"ctx->feeds->count(); \n" .
@@ -264,25 +255,15 @@ class zcfsTemplate
'} unset($fcount); ?>';
}
- public static function FeedsEntriesCount($a)
+ public static function FeedsEntriesCount(ArrayObject $a): string
{
- $none = __('no entries');
- $one = __('one entry');
- $more = __('%d entries');
-
- if (isset($a['none'])) {
- $none = addslashes($a['none']);
- }
- if (isset($a['one'])) {
- $one = addslashes($a['one']);
- }
- if (isset($a['more'])) {
- $more = addslashes($a['more']);
- }
+ $none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no entries';
+ $one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one entry';
+ $more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d entries';
return
"getFeeds(); \n" .
"if (!\$allfeeds->isEmpty()) { \n" .
' while ($allfeeds->fetch()) { ' .
@@ -298,24 +279,14 @@ class zcfsTemplate
'} unset($allfeeds,$fcount); ?>';
}
- public static function FeedEntriesCount($a)
+ public static function FeedEntriesCount(ArrayObject $a): string
{
- $none = 'no entries';
- $one = 'one entry';
- $more = '%d entries';
-
- if (isset($a['none'])) {
- $none = addslashes($a['none']);
- }
- if (isset($a['one'])) {
- $one = addslashes($a['one']);
- }
- if (isset($a['more'])) {
- $more = addslashes($a['more']);
- }
+ $none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no entries';
+ $one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one entry';
+ $more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d entries';
return
- "getPostsByFeed(array('feed_id'=>dcCore::app()->ctx->feeds->feed_id),true)->f(0); \n" .
"if (\$fcount == 0) {\n" .
" printf(__('" . $none . "'),\$fcount);\n" .
@@ -326,21 +297,8 @@ class zcfsTemplate
'} unset($fcount); ?>';
}
- protected static function getValue($a, $v)
+ protected static function getValue(ArrayObject $a, string $v): string
{
return 'tpl->getFilters($a), $v) . '; ?>';
}
-
- protected static function getOperator($op)
- {
- switch (strtolower($op)) {
- case 'or':
- case '||':
- return '||';
- case 'and':
- case '&&':
- default:
- return '&&';
- }
- }
}
diff --git a/src/Uninstall.php b/src/Uninstall.php
index bdb75a5..6333081 100644
--- a/src/Uninstall.php
+++ b/src/Uninstall.php
@@ -10,102 +10,89 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
-$this->addUserAction(
- /* type */
- 'settings',
- /* action */
- 'delete_all',
- /* ns */
- basename(__DIR__),
- /* desc */
- __('delete all settings')
-);
-$this->addUserAction(
- /* type */
- 'tables',
- /* action */
- 'delete',
- /* ns */
- initZoneclearFeedServer::TABLE_NAME,
- /* desc */
- __('delete table')
-);
-$this->addUserAction(
- /* type */
- 'plugins',
- /* action */
- 'delete',
- /* ns */
- basename(__DIR__),
- /* desc */
- __('delete plugin files')
-);
-$this->addUserAction(
- /* type */
- 'versions',
- /* action */
- 'delete',
- /* ns */
- basename(__DIR__),
- /* desc */
- __('delete the version number')
-);
+namespace Dotclear\Plugin\zoneclearFeedServer;
-$this->addDirectAction(
- /* type */
- 'settings',
- /* action */
- 'delete_all',
- /* ns */
- basename(__DIR__),
- /* desc */
- sprintf(__('delete all %s settings'), basename(__DIR__))
-);
-$this->addDirectAction(
- /* type */
- 'tables',
- /* action */
- 'delete',
- /* ns */
- initZoneclearFeedServer::TABLE_NAME,
- /* desc */
- sprintf(__('delete %s table'), basename(__DIR__))
-);
-$this->addDirectAction(
- /* type */
- 'plugins',
- /* action */
- 'delete',
- /* ns */
- basename(__DIR__),
- /* desc */
- sprintf(__('delete %s plugin files'), basename(__DIR__))
-);
-$this->addDirectAction(
- /* type */
- 'versions',
- /* action */
- 'delete',
- /* ns */
- basename(__DIR__),
- /* desc */
- sprintf(__('delete %s version number'), basename(__DIR__))
-);
-$this->addDirectCallback(
- /* function */
- 'zoneclearfeedServerUninstall',
- /* desc */
- 'delete feeds relations'
-);
+use dcCore;
+use dcNsProcess;
+use Dotclear\Plugin\Uninstaller\Uninstaller;
-function zoneclearfeedServerUninstall($id)
+/**
+ * Plugin Uninstaller actions.
+ */
+class Uninstall extends dcNsProcess
{
- if ($id != basename(__DIR__)) {
- return null;
+ public static function init(): bool
+ {
+ static::$init = defined('DC_CONTEXT_ADMIN');
+
+ return static::$init;
+ }
+
+ public static function process(): bool
+ {
+ if (!static::$init || !dcCore::app()->plugins->moduleExists('Uninstaller')) {
+ return false;
+ }
+
+ if (!empty($_POST[My::id() . 'DeletePostsMeta'])) {
+ ZoneclearFeedServer::instance()::deletePostsMeta(null);
+ }
+
+ Uninstaller::instance()
+ ->addUserAction(
+ 'settings',
+ 'delete_all',
+ My::id()
+ )
+ ->addUserAction(
+ My::id() . 'DeletePostsMeta',
+ 'delete_all',
+ My::id()
+ )
+ ->addUserAction(
+ 'tables',
+ 'delete',
+ My::TABLE_NAME
+ )
+ ->addUserAction(
+ 'plugins',
+ 'delete',
+ My::id()
+ )
+ ->addUserAction(
+ 'versions',
+ 'delete',
+ My::id()
+ )
+ ->addDirectAction(
+ 'settings',
+ 'delete_all',
+ My::id()
+ )
+ ->addDirectAction(
+ My::id() . 'DeletePostsMeta',
+ 'delete_all',
+ My::id()
+ )
+ ->addDirectAction(
+ 'tables',
+ 'delete',
+ My::TABLE_NAME
+ )
+ ->addDirectAction(
+ 'plugins',
+ 'delete',
+ My::id()
+ )
+ ->addDirectAction(
+ 'versions',
+ 'delete',
+ My::id()
+ )
+ ;
+
+ return false;
}
- //...
}
diff --git a/src/UninstallCleaner.php b/src/UninstallCleaner.php
new file mode 100644
index 0000000..85f9b41
--- /dev/null
+++ b/src/UninstallCleaner.php
@@ -0,0 +1,104 @@
+set(new UninstallCleaner());
+ }
+
+ public function __construct()
+ {
+ parent::__construct(new CleanerDescriptor(
+ id: My::id() . 'DeletePostsMeta',
+ name: __('Feed Server'),
+ desc: __('Feed server posts feed metadata'),
+ actions: [
+ new ActionDescriptor(
+ id: 'delete_all',
+ select: __('delete selected posts feed metadata'),
+ query: __('delete "%s" posts feed metadata'),
+ success: __('"%s" posts feed metadata deleted'),
+ error: __('Failed to delete "%s" posts feed metadata')
+ ),
+ ]
+ ));
+ }
+
+ public function distributed(): array
+ {
+ return [];
+ }
+
+ public function values(): array
+ {
+ $sql = new SelectStatement();
+ $sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
+ ->columns([
+ $sql->as($sql->count('*'), 'counter'),
+ ])
+ ->where($sql->like('meta_type', My::META_PREFIX . '%'));
+
+ $rs = $sql->select();
+ if (is_null($rs) || $rs->isEmpty() || !is_numeric($rs->f('counter'))) {
+ return [];
+ }
+
+ $res = [];
+ while ($rs->fetch()) {
+ $res[] = new ValueDescriptor(
+ ns: My::id(),
+ count: (int) $rs->f('counter')
+ );
+ }
+
+ return $res;
+ }
+
+ public function execute(string $action, string $ns): bool
+ {
+ if ($action == 'delete_all') {
+ $sql = new DeleteStatement();
+ $sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
+ ->where($sql->like('meta_type', My::META_PREFIX . '%'))
+ ->delete();
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Upgrade.php b/src/Upgrade.php
index 1a3e18d..76699c9 100644
--- a/src/Upgrade.php
+++ b/src/Upgrade.php
@@ -10,27 +10,46 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_CONTEXT_ADMIN')) {
- return null;
-}
+declare(strict_types=1);
-class zcfsUpgrade
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use dcCore;
+use dcNamespace;
+use Dotclear\Database\Statement\{
+ SelectStatement,
+ UpdateStatement
+};
+use Exception;
+
+/**
+ * Module versions upgrades.
+ */
+class Upgrade
{
- public static function preUpgrade()
+ public static function preUpgrade(): void
{
- $current = dcCore::app()->getVersion(basename(dirname('../' . __DIR__)));
- if ($current && version_compare($current, '2022.12.10', '<')) {
+ $current = dcCore::app()->getVersion(My::id());
+ if (!is_string($current) || empty($current)) {
+ return;
+ }
+
+ if (version_compare($current, '2022.12.10', '<')) {
self::preUpgrade20221210();
}
+
+ if (version_compare($current, '2023.05.05', '<')) {
+ self::preUpgrade20230505();
+ }
}
- protected static function preUpgrade20221210()
+ protected static function preUpgrade20221210(): void
{
// Rename settings
$setting_ids = [
'zoneclearFeedServer_active' => 'active',
'zoneclearFeedServer_pub_active' => 'pub_active',
- 'zoneclearFeedServer_post_status_new' => 'psot_new_status',
+ 'zoneclearFeedServer_post_status_new' => 'post_new_status',
'zoneclearFeedServer_bhv_pub_upd' => 'bhv_pub_upd',
'zoneclearFeedServer_update_limit' => 'update_limit',
'zoneclearFeedServer_keep_empty_feed' => 'keep_empty_feed',
@@ -40,23 +59,36 @@ class zcfsUpgrade
'zoneclearFeedServer_post_title_redir' => 'post_title_redir',
];
+ $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
foreach ($setting_ids as $old => $new) {
- $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
- $cur->setting_id = $new;
- $cur->update("WHERE setting_id = '" . $old . "' and setting_ns = 'zoneclearFeedServer' ");
+ $cur->clean();
+ $cur->setField('setting_id', $new);
+ $cur->setField('setting_ns', My::id());
+
+ $sql = new UpdateStatement();
+ $sql
+ ->where('setting_id = ' . $sql->quote($old))
+ ->and('setting_ns = ' . $sql->quote('zoneclearFeedServer'))
+ ->update();
}
// use json rather than serialise for settings array
+ $sql = new SelectStatement();
+ $record = $sql
+ ->from(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME)
+ ->where('setting_ns = ' . $sql->quote(My::id()))
+ ->select();
+
+ if (is_null($record)) {
+ return;
+ }
+
$setting_values = [
'post_full_tpl' => ['post', 'category', 'tag', 'archive'],
'post_title_redir' => ['feed'],
];
- $record = dcCore::app()->con->select(
- 'SELECT * FROM ' . dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME . ' ' .
- "WHERE setting_ns = '" . dcCore::app()->con->escape(basename(dirname('../' . __DIR__))) . "' "
- );
-
+ $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
while ($record->fetch()) {
foreach ($setting_values as $key => $default) {
try {
@@ -65,13 +97,31 @@ class zcfsUpgrade
$value = $default;
}
- $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
- $cur->setting_value = json_encode(!is_array($value) ? $default : $value);
- $cur->update(
- "WHERE setting_id = '" . $key . "' and setting_ns = '" . dcCore::app()->con->escape($record->setting_ns) . "' " .
- 'AND blog_id ' . (null === $record->blog_id ? 'IS NULL ' : ("= '" . dcCore::app()->con->escape($record->blog_id) . "' "))
- );
+ $cur->clean();
+ $cur->setField('setting_value', json_encode(!is_array($value) ? $default : $value));
+
+ $sql = new UpdateStatement();
+ $sql
+ ->where('setting_id = ' . $sql->quote($key))
+ ->and('setting_ns = ' . $sql->quote($record->f('setting_ns')))
+ ->and('blog_id ' . (null === $record->f('blog_id') ? 'IS NULL ' : ('= ' . $sql->quote($record->f('blog_id')))))
+ ->update();
}
}
}
+
+ protected static function preUpgrade20230505(): void
+ {
+ // change settings type of json string to array
+ $sql = new UpdateStatement();
+ $sql
+ ->ref(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME)
+ ->column('setting_type')
+ ->value('array')
+ ->where('setting_id ' . $sql->in([
+ 'post_full_tpl',
+ 'post_title_redir',
+ ]))
+ ->update();
+ }
}
diff --git a/src/UrlHandler.php b/src/UrlHandler.php
index e6e11ed..e5d216e 100644
--- a/src/UrlHandler.php
+++ b/src/UrlHandler.php
@@ -10,41 +10,44 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
+declare(strict_types=1);
+
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use dcCore;
+use dcUrlHandlers;
+use Dotclear\Helper\Html\Html;
+use Exception;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Mix your blog with a feeds planet - url handler methods.
- * @since 2.6
+ * Frontend URL handler.
+ *
+ * This adds public page that list feeds.
+ * And serve an endpoint to update feeds through js.
*/
-class zcfsUrlHandler extends dcUrlHandlers
+class UrlHandler extends dcUrlHandlers
{
/**
* Feeds source page and update methods.
*
- * @param array $args Page arguments
- * @return mixed
+ * @param string $args The page arguments
*/
- public static function zcFeedsPage($args)
+ public static function zoneclearFeedsPage(string $args): void
{
- $s = dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)));
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
# Not active
- if (!$s->active) {
+ if (is_null(dcCore::app()->blog) || !$s->active) {
self::p404();
-
- return null;
}
# Update feeds (from ajax or other post resquest)
if ($args == '/zcfsupd' && 3 == $s->bhv_pub_upd) {
$msg = '';
- if (!empty($_POST['blogId']) && html::escapeJS(dcCore::app()->blog->id) == $_POST['blogId']) {
+ if (!empty($_POST['blogId']) && Html::escapeJS(dcCore::app()->blog->id) == $_POST['blogId']) {
try {
- $zc = new zoneclearFeedServer();
- if ($zc->checkFeedsUpdate()) {
+ if ($z->checkFeedsUpdate()) {
$msg = sprintf(
'%s%s',
'ok',
@@ -73,7 +76,7 @@ class zcfsUrlHandler extends dcUrlHandlers
# Server js
} elseif ($args == '/zcfsupd.js' && 3 == $s->bhv_pub_upd) {
- dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), __DIR__ . '/default-templates');
+ dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), My::path() . '/default-templates');
self::serveDocument(
'zcfsupd.js',
'text/javascript',
@@ -83,8 +86,12 @@ class zcfsUrlHandler extends dcUrlHandlers
# Server feeds description page
} elseif (in_array($args, ['', '/']) && $s->pub_active) {
- $tplset = dcCore::app()->themes->moduleInfo(dcCore::app()->blog->settings->system->theme, 'tplset');
- $path = __DIR__ . '/default-templates/';
+ $theme = dcCore::app()->blog->settings->get('system')->get('theme');
+ if (!is_string($theme)) {
+ self::p404();
+ }
+ $tplset = dcCore::app()->themes->moduleInfo($theme, 'tplset');
+ $path = My::path() . '/default-templates/';
if (!empty($tplset) && is_dir($path . $tplset)) {
dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), $path . $tplset);
} else {
@@ -96,7 +103,5 @@ class zcfsUrlHandler extends dcUrlHandlers
else {
self::p404();
}
-
- return null;
}
}
diff --git a/src/Widgets.php b/src/Widgets.php
index 5811f54..299fcaf 100644
--- a/src/Widgets.php
+++ b/src/Widgets.php
@@ -10,38 +10,33 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('DC_RC_PATH')) {
- return null;
-}
+declare(strict_types=1);
-dcCore::app()->addBehavior(
- 'initWidgets',
- ['zoneclearFeedServerWidget', 'adminSource']
-);
-dcCore::app()->addBehavior(
- 'initWidgets',
- ['zoneclearFeedServerWidget', 'adminNumber']
-);
+namespace Dotclear\Plugin\zoneclearFeedServer;
+
+use dcCore;
+use Dotclear\Helper\Html\Html;
+use Dotclear\Plugin\widgets\WidgetsStack;
+use Dotclear\Plugin\widgets\WidgetsElement;
/**
- * @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
- * @brief Mix your blog with a feeds planet - widgets methods.
- * @since 2.6
+ * Widgets.
+ *
+ * A widget to list feeds source.
+ * A widget to list feeds statistics.
*/
-class zoneclearFeedServerWidget
+class Widgets
{
/**
- * Widget configuration for sources list.
- *
- * @param dcWidgets $w dcWidgets instance
+ * @param WidgetsStack $w WidgetsStack instance
*/
- public static function adminSource($w)
+ public static function init(WidgetsStack $w): void
{
$w
->create(
'zcfssource',
__('Feeds server: sources'),
- ['zoneclearFeedServerWidget', 'publicSource'],
+ [self::class, 'publicSource'],
null,
__('List sources of feeds')
)
@@ -85,20 +80,12 @@ class zoneclearFeedServerWidget
->addContentOnly()
->addClass()
->addOffline();
- }
- /**
- * Widget configuration for feeds info.
- *
- * @param dcWidgets $w dcWidgets instance
- */
- public static function adminNumber($w)
- {
$w
->create(
'zcfsnumber',
__('Feeds server: numbers'),
- ['zoneclearFeedServerWidget', 'publicNumber'],
+ [self::class, 'publicNumber'],
null,
__('Show some numbers about feeds')
)
@@ -144,59 +131,58 @@ class zoneclearFeedServerWidget
/**
* Widget for sources list.
*
- * @param dcWidget $w dcWidget instance
+ * @param WidgetsElement $w Widgets Element instance
*/
- public static function publicSource($w)
+ public static function publicSource(WidgetsElement $w): string
{
- if ($w->offline) {
- return null;
- }
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
- if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
+ if ($w->__get('offline')
+ || !$s->active
|| !$w->checkHomeOnly(dcCore::app()->url->type)
) {
- return null;
+ return '';
}
$p = [];
- $p['order'] = ($w->sortby && in_array($w->sortby, ['feed_upd_last', 'lowername', 'feed_creadt'])) ?
- $w->sortby . ' ' : 'feed_upd_last ';
- $p['order'] .= $w->sort == 'desc' ? 'DESC' : 'ASC';
- $p['limit'] = abs((int) $w->limit);
+ $p['order'] = ($w->__get('sortby') && in_array($w->__get('sortby'), ['feed_upd_last', 'lowername', 'feed_creadt'])) ?
+ $w->__get('sortby') . ' ' : 'feed_upd_last ';
+ $p['order'] .= $w->__get('sort') == 'desc' ? 'DESC' : 'ASC';
+ $p['limit'] = is_numeric($w->__get('limit')) ? abs((int) $w->__get('limit')) : 10;
$p['feed_status'] = 1;
- $zc = new zoneclearFeedServer();
- $rs = $zc->getFeeds($p);
-
+ $rs = $z->getFeeds($p);
if ($rs->isEmpty()) {
- return null;
+ return '';
}
$lines = [];
$i = 1;
while ($rs->fetch()) {
+ $row = new FeedRow($rs);
$lines[] = sprintf(
'%s',
- $rs->feed_url,
- $rs->feed_owner,
- $rs->feed_name
+ $row->url,
+ $row->owner,
+ $row->name
);
$i++;
}
$pub = '';
- if ($w->pagelink && dcCore::app()->blog->settings->__get(basename(__DIR__))->pub_active) {
+ if ($w->__get('pagelink') && $s->pub_active) {
$pub = sprintf(
'%s
',
- dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
- html::escapeHTML($w->pagelink)
+ dcCore::app()->blog?->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
+ Html::escapeHTML(is_string($w->__get('pagelink')) ? $w->__get('pagelink') : '')
);
}
return $w->renderDiv(
- $w->content_only,
- 'zoneclear-sources ' . $w->class,
+ (bool) $w->__get('content_only'),
+ 'zoneclear-sources ' . $w->__get('class'),
'',
- ($w->title ? $w->renderTitle(html::escapeHTML($w->title)) : '') .
+ ($w->__get('title') ? $w->renderTitle(Html::escapeHTML(is_string($w->__get('title')) ? $w->__get('title') : '')) : '') .
sprintf('', implode('', $lines)) . $pub
);
}
@@ -204,38 +190,38 @@ class zoneclearFeedServerWidget
/**
* Widget for feeds info.
*
- * @param dcWidget $w dcWidget instance
+ * @param WidgetsElement $w Widgets Element instance
*/
- public static function publicNumber($w)
+ public static function publicNumber(WidgetsElement $w): string
{
- if ($w->offline) {
- return;
- }
+ $z = ZoneclearFeedServer::instance();
+ $s = $z->settings;
- if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
+ if ($w->__get('offline')
+ || !$s->active
|| !$w->checkHomeOnly(dcCore::app()->url->type)
) {
- return null;
+ return '';
}
- $zc = new zoneclearFeedServer();
$content = '';
# Feed
- if ($w->feed_show) {
- $title = ($w->feed_title ? sprintf(
+ if ($w->__get('feed_show')) {
+ $title = ($w->__get('feed_title') ? sprintf(
'%s ',
- html::escapeHTML($w->feed_title)
+ Html::escapeHTML(is_string($w->__get('feed_title')) ? $w->__get('feed_title') : '')
) : '');
- $count = $zc->getFeeds([], true)->f(0);
+ $count = $z->getFeeds([], true)->f(0);
+ $count = is_numeric($count) ? (int) $count : 0;
$text = $count ? sprintf(__('one source', '%d sources', $count), $count) : __('no sources');
- if (dcCore::app()->blog->settings->__get(basename(__DIR__))->pub_active) {
+ if ($s->pub_active) {
$text = sprintf(
'%s',
- dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
+ dcCore::app()->blog?->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
$text
);
}
@@ -244,18 +230,21 @@ class zoneclearFeedServerWidget
}
# Entry
- if ($w->entry_show) {
+ if ($w->__get('entry_show')) {
$count = 0;
- $feeds = $zc->getFeeds();
+ $feeds = $z->getFeeds();
if (!$feeds->isEmpty()) {
while ($feeds->fetch()) {
- $count += (int) $zc->getPostsByFeed(['feed_id' => $feeds->feed_id], true)->f(0);
+ $fid = is_numeric($feeds->f('feed_id')) ? (int) $feeds->f('feed_id') : 0;
+ $c = $z->getPostsByFeed(['feed_id' => $fid], true)->f(0);
+ $c = is_numeric($c) ? (int) $c : 0;
+ $count += $c;
}
}
- $title = ($w->entry_title ? sprintf(
+ $title = ($w->__get('entry_title') ? sprintf(
'%s ',
- html::escapeHTML($w->entry_title)
+ Html::escapeHTML(is_string($w->__get('entry_title')) ? $w->__get('entry_title') : '')
) : '');
$text = $count ? sprintf(__('one entry', '%d entries', $count), $count) : __('no entries');
@@ -264,15 +253,15 @@ class zoneclearFeedServerWidget
}
if (!$content) {
- return null;
+ return '';
}
# Display
return $w->renderDiv(
- $w->content_only,
- 'zoneclear-number ' . $w->class,
+ (bool) $w->__get('content_only'),
+ 'zoneclear-number ' . $w->__get('class'),
'',
- ($w->title ? $w->renderTitle(html::escapeHTML($w->title)) : '') .
+ ($w->__get('title') ? $w->renderTitle(Html::escapeHTML(is_string($w->__get('title')) ? $w->__get('title') : '')) : '') .
sprintf('', $content)
);
}
diff --git a/src/ZoneclearFeedServer.php b/src/ZoneclearFeedServer.php
new file mode 100644
index 0000000..56c01ec
--- /dev/null
+++ b/src/ZoneclearFeedServer.php
@@ -0,0 +1,976 @@
+settings = Settings::instance();
+ }
+
+ /**
+ * Get class instance.
+ *
+ * @return ZoneclearFeedServer Self instacne
+ */
+ public static function instance(): ZoneclearFeedServer
+ {
+ if (!(self::$instance instanceof ZoneclearFeedServer)) {
+ self::$instance = new ZoneclearFeedServer();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Open database table cursor.
+ *
+ * @return Cursor The cursor
+ */
+ public function openCursor(): Cursor
+ {
+ return dcCore::app()->con->openCursor(dcCore::app()->prefix . My::TABLE_NAME);
+ }
+
+ /**
+ * Update feed record.
+ *
+ * @param int $id The feed ID
+ * @param Cursor $cur The cursor instance
+ */
+ public function updateFeed(int $id, Cursor $cur): void
+ {
+ dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
+
+ try {
+ if ($id < 1) {
+ throw new Exception(__('No such ID'));
+ }
+
+ $cur->setField('feed_upddt', date('Y-m-d H:i:s'));
+
+ $cur->update(sprintf(
+ "WHERE feed_id = %s AND blog_id = '%s' ",
+ $id,
+ dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id)
+ ));
+ dcCore::app()->con->unlock();
+ $this->trigger();
+ } catch (Exception $e) {
+ dcCore::app()->con->unlock();
+
+ throw $e;
+ }
+
+ # --BEHAVIOR-- zoneclearFeedServerAfterUpdateFeed -- Cursor, int
+ dcCore::app()->callBehavior('zoneclearFeedServerAfterUpdateFeed', $cur, $id);
+ }
+
+ /**
+ * Add feed record.
+ *
+ * @param Cursor $cur The cursor
+ *
+ * @return int The new feed ID
+ */
+ public function addFeed(Cursor $cur): int
+ {
+ dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
+
+ try {
+ $cur->setField('feed_id', $this->getNextId());
+ $cur->setField('blog_id', dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id));
+ $cur->setField('feed_creadt', date('Y-m-d H:i:s'));
+ $cur->setField('feed_upddt', date('Y-m-d H:i:s'));
+
+ //add getFeedCursor here
+
+ $cur->insert();
+ dcCore::app()->con->unlock();
+ $this->trigger();
+ } catch (Exception $e) {
+ dcCore::app()->con->unlock();
+
+ throw $e;
+ }
+
+ $id = is_numeric($cur->getField('feed_id')) ? (int) $cur->getField('feed_id') : 0;
+
+ # --BEHAVIOR-- zoneclearFeedServerAfterAddFeed -- Cursor, int
+ dcCore::app()->callBehavior('zoneclearFeedServerAfterAddFeed', $cur, $id);
+
+ return $id;
+ }
+
+ /**
+ * Quick enable / disable feed.
+ *
+ * @param int $id The feed ID
+ * @param bool $enable Enable feed
+ * @param int $time Force update time
+ */
+ public function enableFeed(int $id, bool $enable = true, int $time = 0): void
+ {
+ try {
+ if ($id < 1) {
+ throw new Exception(__('No such ID'));
+ }
+
+ $cur = $this->openCursor();
+ dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
+
+ $cur->setField('feed_upddt', date('Y-m-d H:i:s'));
+ $cur->setField('feed_status', (int) $enable);
+ if (0 < $time) {
+ $cur->setField('feed_upd_last', $time);
+ }
+
+ $cur->update(sprintf(
+ "WHERE feed_id = %s AND blog_id = '%s' ",
+ $id,
+ dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id)
+ ));
+ dcCore::app()->con->unlock();
+ $this->trigger();
+ } catch (Exception $e) {
+ dcCore::app()->con->unlock();
+
+ throw $e;
+ }
+
+ # --BEHAVIOR-- zoneclearFeedServerAfterEnableFeed -- int, bool, int
+ dcCore::app()->callBehavior('zoneclearFeedServerAfterEnableFeed', $id, $enable, $time);
+ }
+
+ #
+ /**
+ * Delete record (this not deletes post).
+ *
+ * @param int $id The feed ID
+ */
+ public function deleteFeed(int $id): void
+ {
+ if ($id < 1) {
+ throw new Exception(__('No such ID'));
+ }
+
+ # --BEHAVIOR-- zoneclearFeedServerBeforeDeleteFeed -- int
+ dcCore::app()->callBehavior('zoneclearFeedServerBeforeDeleteFeed', $id);
+
+ $sql = new DeleteStatement();
+ $sql->from(dcCore::app()->prefix . My::TABLE_NAME)
+ ->where('feed_id ' . $sql->in($id))
+ ->and('blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
+ ->delete();
+
+ $this->trigger();
+ }
+
+ /**
+ * Delete all post(s) meta.
+ *
+ * This deletes all post(s) related meta,
+ * the post from the planet become an ordinary post.
+ *
+ * @param null|int $id The post ID (or null for all!)
+ */
+ public static function deletePostsMeta(?int $id): void
+ {
+ $sql = new DeleteStatement();
+ $sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
+ ->where('meta_type ' . $sql->in([
+ My::META_PREFIX . 'url',
+ My::META_PREFIX . 'author',
+ My::META_PREFIX . 'site',
+ My::META_PREFIX . 'sitename',
+ My::META_PREFIX . 'id',
+ ]));
+
+ if (!is_null($post_id)) {
+ $sql->and('post_id = ' . $id);
+ }
+
+ $sql->delete();
+ }
+
+ /**
+ * Get related posts.
+ *
+ * @param array $params The query params
+ * @param bool $count_only Return only result count
+ *
+ * @return MetaRecord The record instance
+ */
+ public function getPostsByFeed(array $params = [], bool $count_only = false): MetaRecord
+ {
+ if (!isset($params['feed_id']) || !is_numeric($params['feed_id'])) {
+ return MetaRecord::newFromArray([]);
+ }
+
+ $sql = new SelectStatement();
+ $sql->join(
+ (new JoinStatement())
+ ->left()
+ ->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' F')
+ ->on('P.post_id = F.post_id')
+ ->statement()
+ );
+
+ $params['sql'] = "AND P.blog_id = '" . dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id) . "' " .
+ "AND F.meta_type = '" . My::META_PREFIX . "id' " .
+ "AND F.meta_id = '" . dcCore::app()->con->escapeStr((string) $params['feed_id']) . "' ";
+
+ unset($params['feed_id']);
+
+ $rs = dcCore::app()->blog?->getPosts($params, $count_only, $sql);
+
+ return is_null($rs) ? MetaRecord::newFromArray([]) : $rs;
+ }
+
+ /**
+ * Get feed record.
+ *
+ * @param array $params The query params
+ * @param bool $count_only Return only result count
+ *
+ * @return MetaRecord The record instance
+ */
+ public function getFeeds(array $params = [], bool $count_only = false): MetaRecord
+ {
+ if ($count_only) {
+ $strReq = 'SELECT count(Z.feed_id) ';
+ } else {
+ $content_req = '';
+ if (!empty($params['columns']) && is_array($params['columns'])) {
+ $content_req .= implode(', ', $params['columns']) . ', ';
+ }
+
+ $strReq = 'SELECT Z.feed_id, Z.feed_creadt, Z.feed_upddt, Z.feed_type, ' .
+ 'Z.blog_id, Z.cat_id, ' .
+ 'Z.feed_upd_int, Z.feed_upd_last, Z.feed_status, ' .
+ $content_req .
+ 'LOWER(Z.feed_name) as lowername, Z.feed_name, Z.feed_desc, ' .
+ 'Z.feed_url, Z.feed_feed, Z.feed_get_tags, ' .
+ 'Z.feed_tags, Z.feed_owner, Z.feed_tweeter, Z.feed_lang, ' .
+ 'Z.feed_nb_out, Z.feed_nb_in, ' .
+ 'C.cat_title, C.cat_url, C.cat_desc ';
+ }
+
+ $strReq .= 'FROM ' . dcCore::app()->prefix . My::TABLE_NAME . ' Z ' .
+ 'LEFT OUTER JOIN ' . dcCore::app()->prefix . dcCategories::CATEGORY_TABLE_NAME . ' C ON Z.cat_id = C.cat_id ';
+
+ if (!empty($params['from']) && is_string($params['from'])) {
+ $strReq .= $params['from'] . ' ';
+ }
+
+ $strReq .= "WHERE Z.blog_id = '" . dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id) . "' ";
+
+ if (isset($params['feed_type']) && is_string($params['feed_type'])) {
+ $strReq .= "AND Z.feed_type = '" . dcCore::app()->con->escapeStr((string) $params['feed_type']) . "' ";
+ } else {
+ $strReq .= "AND Z.feed_type = 'feed' ";
+ }
+
+ if (!empty($params['feed_id'])) {
+ if (is_array($params['feed_id'])) {
+ array_walk($params['feed_id'], function (&$v, $k) { if ($v !== null) { $v = (int) $v; }});
+ } elseif (is_numeric($params['feed_id'])) {
+ $params['feed_id'] = [(int) $params['feed_id']];
+ }
+ $strReq .= 'AND Z.feed_id ' . dcCore::app()->con->in($params['feed_id']);
+ }
+
+ if (isset($params['feed_feed']) && is_string($params['feed_feed'])) {
+ $strReq .= "AND Z.feed_feed = '" . dcCore::app()->con->escapeStr((string) $params['feed_feed']) . "' ";
+ }
+ if (isset($params['feed_url']) && is_string($params['feed_url'])) {
+ $strReq .= "AND Z.feed_url = '" . dcCore::app()->con->escapeStr((string) $params['feed_url']) . "' ";
+ }
+ if (isset($params['feed_status'])) {
+ $strReq .= 'AND Z.feed_status = ' . ((int) $params['feed_status']) . ' ';
+ }
+
+ if (!empty($params['q']) && is_string($params['q'])) {
+ $q = dcCore::app()->con->escapeStr((string) str_replace('*', '%', strtolower($params['q'])));
+ $strReq .= "AND LOWER(Z.feed_name) LIKE '" . $q . "' ";
+ }
+
+ if (!empty($params['sql']) && is_string($params['sql'])) {
+ $strReq .= $params['sql'] . ' ';
+ }
+
+ if (!$count_only) {
+ if (!empty($params['order']) && is_string($params['order'])) {
+ $strReq .= 'ORDER BY ' . dcCore::app()->con->escapeStr((string) $params['order']) . ' ';
+ } else {
+ $strReq .= 'ORDER BY Z.feed_upddt DESC ';
+ }
+ }
+
+ if (!$count_only && isset($params['limit'])) {
+ if (is_numeric($params['limit'])) {
+ $params['limit'] = (int) $params['limit'];
+ }
+ if (is_int($params['limit']) || is_array($params['limit'])) {
+ $strReq .= dcCore::app()->con->limit($params['limit']);
+ }
+ }
+
+ return new MetaRecord(dcCore::app()->con->select($strReq));
+ }
+
+ /**
+ * Get next table id.
+ *
+ * @return int THe next ID
+ */
+ private function getNextId(): int
+ {
+ $sql = new SelectStatement();
+ $rs = $sql
+ ->column($sql->max('feed_id'))
+ ->from(dcCore::app()->prefix . My::TABLE_NAME)
+ ->select();
+
+ return (int) $rs?->f(0) + 1;
+ }
+
+ /**
+ * Lock a file to see if an update is ongoing.
+ *
+ * @return bool True if file is locked
+ */
+ public function lockUpdate()
+ {
+ try {
+ # Need flock function
+ if (!function_exists('flock')) {
+ throw new Exception("Can't call php function named flock");
+ }
+ # Cache writable ?
+ if (!is_writable(DC_TPL_CACHE)) {
+ throw new Exception("Can't write in cache fodler");
+ }
+ # Set file path
+ $f_md5 = md5((string) dcCore::app()->blog?->id);
+ $cached_file = sprintf(
+ '%s/%s/%s/%s/%s.txt',
+ DC_TPL_CACHE,
+ 'periodical',
+ substr($f_md5, 0, 2),
+ substr($f_md5, 2, 2),
+ $f_md5
+ );
+ # Real path
+ $cached_file = Path::real($cached_file, false);
+ if (false === $cached_file) {
+ throw new Exception("Can't get cache file path");
+ }
+ # Make dir
+ if (!is_dir(dirname($cached_file))) {
+ Files::makeDir(dirname($cached_file), true);
+ }
+ # Make file
+ if (!file_exists($cached_file)) {
+ !$fp = @fopen($cached_file, 'w');
+ if ($fp === false) {
+ throw new Exception("Can't create file");
+ }
+ fwrite($fp, '1', strlen('1'));
+ fclose($fp);
+ }
+ # Open file
+ if (!($fp = @fopen($cached_file, 'r+'))) {
+ throw new Exception("Can't open file");
+ }
+ # Lock file
+ if (!flock($fp, LOCK_EX)) {
+ throw new Exception("Can't lock file");
+ }
+ self::$lock = $fp;
+
+ return true;
+ } catch (Exception $e) {
+ throw $e;
+ }
+ }
+
+ /**
+ * Unlock file of update process.
+ */
+ public function unlockUpdate(): void
+ {
+ if (!is_null(self::$lock)) {
+ @fclose(self::$lock);
+ self::$lock = null;
+ }
+ }
+
+ /**
+ * Check and add/update post related to record if needed.
+ *
+ * @param int $id The feed ID
+ * @param bool $throw Throw exception or end silently
+ *
+ * @return bool True on success
+ */
+ public function checkFeedsUpdate(int $id = 0, bool $throw = false): bool
+ {
+ $s = $this->settings;
+
+ # Not configured
+ if (is_null(dcCore::app()->auth) || !$s->active || !$s->user) {
+ return false;
+ }
+
+ # Limit to one update at a time
+ try {
+ $this->lockUpdate();
+ } catch (Exception $e) {
+ if ($throw) {
+ throw $e;
+ }
+
+ return false;
+ }
+
+ $tz = dcCore::app()->blog?->settings->get('system')->get('blog_timezone');
+ Date::setTZ(is_string($tz) ? $tz : 'UTC');
+ $time = time();
+
+ # All feeds or only one (from admin)
+ $f = !$id ?
+ $this->getFeeds(['feed_status' => 1, 'order' => 'feed_upd_last ASC']) :
+ $this->getFeeds(['feed_id' => $id]);
+
+ # No feed
+ if ($f->isEmpty()) {
+ return false;
+ }
+
+ $enabled = false;
+ $updates = false;
+ $loop_mem = [];
+
+ $i = 0;
+
+ $cur_post = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME);
+ $cur_meta = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcMeta::META_TABLE_NAME);
+
+ while ($f->fetch()) {
+ $row = new FeedRow($f);
+ # Check if feed need update
+ if ($id
+ || $i < $s->update_limit && $row->status == 1 && ($time > $row->upd_last + $row->upd_int)
+ ) {
+ if (!$enabled) {
+ # Set feeds user
+ $this->enableUser(true);
+ $enabled = true;
+ }
+ $i++;
+ $feed = self::readFeed($row->feed);
+
+ # Nothing to parse
+ if (!$feed) {
+ # Keep active empty feed or disable it ?
+ if (!$s->keep_empty_feed) {
+ $this->enableFeed($row->id, false);
+ } else {
+ # Set update time of this feed
+ $this->enableFeed($row->id, true, $time);
+ }
+ $i++;
+
+ # Not updated since last visit
+ } elseif (!$id
+ && '' != $feed->pubdate
+ && strtotime($feed->pubdate) < $row->upd_last
+ ) {
+ # Set update time of this feed
+ $this->enableFeed($row->id, true, $time);
+ $i++;
+ } else {
+ # Set update time of this feed
+ $this->enableFeed($row->id, (bool) $row->status, $time);
+
+ dcCore::app()->con->begin();
+
+ foreach ($feed->items as $item) {
+ $item_TS = $item->TS ? $item->TS : $time;
+
+ // I found that mercurial atom feed did not repect standard
+ $item_link = @$item->link;
+ if (!$item_link) {
+ $item_link = @$item->guid;
+ }
+ # Unknow feed item link
+ if (!$item_link) {
+ continue;
+ }
+
+ $item_link = dcCore::app()->con->escapeStr((string) $item_link);
+ $is_new_published_entry = false;
+
+ # Not updated since last visit
+ if (!$id && $item_TS < $row->upd_last) {
+ continue;
+ }
+
+ # Fix loop twin
+ if (in_array($item_link, $loop_mem)) {
+ continue;
+ }
+ $loop_mem[] = $item_link;
+
+ # Check if entry exists
+ $sql = new SelectStatement();
+ $old_post = $sql
+ ->columns([
+ 'P.post_id',
+ 'P.post_status',
+ ])
+ ->from($sql->as(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME, 'P'))
+ ->join(
+ (new JoinStatement())
+ ->inner()
+ ->from($sql->as(dcCore::app()->prefix . dcMeta::META_TABLE_NAME, 'M'))
+ ->on('P.post_id = M.post_id')
+ ->statement()
+ )
+ ->where('blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
+ ->and("meta_type = '" . My::META_PREFIX . "url'")
+ ->and('meta_id = ' . $sql->quote($item_link))
+ ->select();
+
+ if (is_null($old_post)) {
+ $old_post = MetaRecord::newFromArray([]);
+ }
+
+ # Prepare entry Cursor
+ $cur_post->clean();
+ $cur_post->setField('post_dt', date('Y-m-d H:i:s', $item_TS));
+ if ($row->cat_id) {
+ $cur_post->setField('cat_id', $row->cat_id);
+ }
+ $post_content = $item->content ? $item->content : $item->description;
+ $cur_post->setField('post_format', 'xhtml');
+ $cur_post->setField('post_content', Html::absoluteURLs($post_content, $feed->link));
+ $cur_post->setField('post_title', $item->title ? $item->title : Text::cutString(Html::clean(is_string($cur_post->getField('post_content')) ? $cur_post->getField('post_content') : ''), 60));
+ $creator = $item->creator ? $item->creator : $row->owner;
+
+ try {
+ # Create entry
+ if ($old_post->isEmpty()) {
+ # Post
+ $cur_post->setField('user_id', dcCore::app()->auth->userID());
+ $cur_post->setField('post_format', 'xhtml');
+ $cur_post->setField('post_status', $s->post_status_new);
+ $cur_post->setField('post_open_comment', 0);
+ $cur_post->setField('post_open_tb', 0);
+
+ $post_id = dcCore::app()->auth->sudo(
+ [dcCore::app()->blog, 'addPost'],
+ $cur_post
+ );
+
+ # Auto tweet new post
+ if (!empty($cur_post->getField('post_status'))) {
+ $is_new_published_entry = true;
+ }
+
+ # Update entry
+ } else {
+ $post_id = is_numeric($old_post->f('post_id')) ? (int) $old_post->f('post_id') : 0;
+
+ dcCore::app()->auth->sudo(
+ [dcCore::app()->blog, 'updPost'],
+ $post_id,
+ $cur_post
+ );
+
+ # Quick delete old meta
+ $sql = new DeleteStatement();
+ $sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
+ ->where('post_id = ' . $post_id)
+ ->and($sql->like('meta_type', My::META_PREFIX . '%'))
+ ->delete();
+
+ # Delete old tags
+ dcCore::app()->auth->sudo(
+ [dcCore::app()->meta, 'delPostMeta'],
+ $post_id,
+ 'tag'
+ );
+ }
+
+ # Quick add new meta
+
+ $cur_meta->clean();
+ $cur_meta->setField('post_id', $post_id);
+ $cur_meta->setField('meta_type', My::META_PREFIX . 'url');
+ $cur_meta->setField('meta_id', $item_link);
+ $cur_meta->insert();
+
+ $cur_meta->clean();
+ $cur_meta->setField('post_id', $post_id);
+ $cur_meta->setField('meta_type', My::META_PREFIX . 'author');
+ $cur_meta->setField('meta_id', $creator);
+ $cur_meta->insert();
+
+ $cur_meta->clean();
+ $cur_meta->setField('post_id', $post_id);
+ $cur_meta->setField('meta_type', My::META_PREFIX . 'site');
+ $cur_meta->setField('meta_id', $row->url);
+ $cur_meta->insert();
+
+ $cur_meta->clean();
+ $cur_meta->setField('post_id', $post_id);
+ $cur_meta->setField('meta_type', My::META_PREFIX . 'sitename');
+ $cur_meta->setField('meta_id', $row->name);
+ $cur_meta->insert();
+
+ $cur_meta->clean();
+ $cur_meta->setField('post_id', $post_id);
+ $cur_meta->setField('meta_type', My::META_PREFIX . 'id');
+ $cur_meta->setField('meta_id', $row->id);
+ $cur_meta->insert();
+
+ # Add new tags
+ $tags = dcCore::app()->meta->splitMetaValues($row->tags);
+ if ($row->get_tags) {
+ # Some feed subjects contains more than one tag
+ foreach ($item->subject as $subjects) {
+ $tmp = dcCore::app()->meta->splitMetaValues($subjects);
+ $tags = array_merge($tags, $tmp);
+ }
+ $tags = array_unique($tags);
+ }
+ $formated_tags = [];
+ foreach ($tags as $tag) {
+ # Change tags case
+ switch ((int) $s->tag_case) {
+ case 3: $tag = strtoupper($tag);
+
+ break;
+ case 2: $tag = strtolower($tag);
+
+ break;
+ case 1: $tag = ucfirst(strtolower($tag));
+
+ break;
+ default: /* do nothing */ break;
+ }
+ if (!in_array($tag, $formated_tags)) {
+ $formated_tags[] = $tag;
+ dcCore::app()->auth->sudo(
+ [dcCore::app()->meta, 'delPostMeta'],
+ $post_id,
+ 'tag',
+ dcMeta::sanitizeMetaID($tag)
+ );
+ dcCore::app()->auth->sudo(
+ [dcCore::app()->meta, 'setPostMeta'],
+ $post_id,
+ 'tag',
+ dcMeta::sanitizeMetaID($tag)
+ );
+ }
+ }
+ } catch (Exception $e) {
+ dcCore::app()->con->rollback();
+ $this->enableUser(false);
+ $this->unlockUpdate();
+
+ throw $e;
+ }
+
+ # --BEHAVIOR-- zoneclearFeedServerAfterCheckFeedUpdate -- FeedRow
+ dcCore::app()->callBehavior('zoneclearFeedServerAfterCheckFeedUpdate', $row);
+ }
+ dcCore::app()->con->commit();
+ }
+ }
+ }
+ if ($enabled) {
+ $this->enableUser(false);
+ }
+ $this->unlockUpdate();
+
+ return true;
+ }
+
+ /**
+ * Set permission to update post table.
+ *
+ * @param boolean $enable Enable or disable perm
+ */
+ public function enableUser(bool $enable = false): void
+ {
+ if (is_null(dcCore::app()->auth)) {
+ return;
+ }
+
+ # Enable
+ if ($enable) {
+ // backup current user
+ if (!is_null(dcCore::app()->auth->userID()) && !is_string(dcCore::app()->auth->userID())) {
+ throw new Exception('Unable to backup user');
+ }
+ $this->user = dcCore::app()->auth->userID();
+ // set zcfs posts user
+ if (!dcCore::app()->auth->checkUser($this->settings->user)) {
+ throw new Exception('Unable to set user');
+ }
+ # Disable
+ } else {
+ dcCore::app()->auth = null;
+ dcCore::app()->auth = new dcAuth();
+ // restore current user
+ dcCore::app()->auth->checkUser($this->user ?? '');
+ }
+ }
+
+ /**
+ * Read and parse external feeds.
+ *
+ * @param string $url The feed URL
+ *
+ * @return Parser|false The parsed feed
+ */
+ public static function readFeed(string $url): false|Parser
+ {
+ try {
+ $feed_reader = new Reader();
+ $feed_reader->setCacheDir(DC_TPL_CACHE);
+ $feed_reader->setTimeout(self::NET_HTTP_TIMEOUT);
+ $feed_reader->setMaxRedirects(self::NET_HTTP_MAX_REDIRECT);
+ $feed_reader->setUserAgent(self::NET_HTTP_AGENT);
+
+ return $feed_reader->parse($url);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Trigger blog.
+ */
+ private function trigger(): void
+ {
+ dcCore::app()->blog?->triggerBlog();
+ }
+
+ /**
+ * Check if an URL is well formed.
+ *
+ * @param string $url The URL
+ *
+ * @return bool True if URL is allowed
+ */
+ public static function validateURL(string $url): bool
+ {
+ return false !== strpos($url, 'http://')
+ || false !== strpos($url, 'https://');
+ }
+
+ /**
+ * Get full URL.
+ *
+ * Know bugs: anchor is not well parsed.
+ *
+ * @param string $root The root URL
+ * @param string $url A URL
+ *
+ * @return string The parse URL
+ */
+ public static function absoluteURL(string $root, string $url): string
+ {
+ $host = preg_replace(
+ '|^([a-z]{3,}://)(.*?)/(.*)$|',
+ '$1$2',
+ $root
+ );
+
+ $parse = parse_url($url);
+
+ if (empty($parse['scheme'])) {
+ if (strpos($url, '/') === 0) {
+ $url = $host . $url;
+ } elseif (strpos($url, '#') === 0) {
+ $url = $root . $url;
+ } elseif (preg_match('|/$|', $root)) {
+ $url = $root . $url;
+ } else {
+ $url = dirname($root) . '/' . $url;
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * Get list of (super)admins of current blog.
+ *
+ * @return array List of UserCNs/UserIds
+ */
+ public function getAllBlogAdmins(): array
+ {
+ $admins = [];
+
+ # Get super admins
+ $sql = new SelectStatement();
+ $rs = $sql
+ ->from(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME)
+ ->columns([
+ 'user_id',
+ 'user_super',
+ 'user_name',
+ 'user_firstname',
+ 'user_displayname',
+ ])
+ ->where('user_super = 1')
+ ->and('user_status = 1')
+ ->select();
+
+ if (!is_null($rs) && !$rs->isEmpty()) {
+ while ($rs->fetch()) {
+ $user_cn = dcUtils::getUserCN(
+ $rs->f('user_id'),
+ $rs->f('user_name'),
+ $rs->f('user_firstname'),
+ $rs->f('user_displayname')
+ );
+ $admins[$user_cn . ' (super admin)'] = $rs->f('user_id');
+ }
+ }
+
+ # Get admins
+ $sql = new SelectStatement();
+ $rs = $sql
+ ->columns([
+ 'U.user_id',
+ 'U.user_super',
+ 'U.user_name',
+ 'U.user_firstname',
+ 'U.user_displayname',
+ ])
+ ->from($sql->as(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME, 'U'))
+ ->join(
+ (new JoinStatement())
+ ->left()
+ ->from($sql->as(dcCore::app()->prefix . dcAuth::PERMISSIONS_TABLE_NAME, 'P'))
+ ->on('U.user_id = P.user_id')
+ ->statement()
+ )
+ ->where('U.user_status = 1')
+ ->and('P.blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
+ ->and($sql->like('P.permissions', '%|admin|%'))
+ ->select();
+
+ if (!is_null($rs) && !$rs->isEmpty()) {
+ while ($rs->fetch()) {
+ $user_cn = dcUtils::getUserCN(
+ $rs->f('user_id'),
+ $rs->f('user_name'),
+ $rs->f('user_firstname'),
+ $rs->f('user_displayname')
+ );
+ $admins[$user_cn . ' (admin)'] = $rs->f('user_id');
+ }
+ }
+
+ return $admins;
+ }
+
+ /**
+ * Get list of urls where entries could be hacked.
+ *
+ * @return array List of names/types of URLs
+ */
+ public static function getPublicUrlTypes(): array
+ {
+ $types = new ArrayObject([
+ __('Home page') => 'default',
+ __('Entries pages') => 'post',
+ __('Tags pages') => 'tag',
+ __('Archives pages') => 'archive',
+ __('Category pages') => 'category',
+ __('Entries feed') => 'feed',
+ ]);
+
+ # --BEHAVIOR-- zoneclearFeedServerPublicUrlTypes -- ArrayObject
+ dcCore::app()->callBehavior('zoneclearFeedServerPublicUrlTypes', $types);
+
+ return $types->getArrayCopy();
+ }
+}
diff --git a/src/ZoneclearFeedsServer.php b/src/ZoneclearFeedsServer.php
deleted file mode 100644
index 30457a8..0000000
--- a/src/ZoneclearFeedsServer.php
+++ /dev/null
@@ -1,925 +0,0 @@
-con = dcCore::app()->con;
- $this->blog = dcCore::app()->con->escape(dcCore::app()->blog->id);
- $this->table = dcCore::app()->prefix . initZoneclearFeedServer::TABLE_NAME;
- }
-
- /**
- * Short openCursor.
- *
- * @return cursor cursor instance
- */
- public function openCursor()
- {
- return $this->con->openCursor($this->table);
- }
-
- /**
- * Update feed record.
- *
- * @param integer $id Feed id
- * @param cursor $cur cursor instance
- */
- public function updFeed($id, cursor $cur)
- {
- $this->con->writeLock($this->table);
-
- try {
- $id = (int) $id;
-
- if ($id < 1) {
- throw new Exception(__('No such ID'));
- }
-
- $cur->feed_upddt = date('Y-m-d H:i:s');
-
- $cur->update(sprintf(
- "WHERE feed_id = %s AND blog_id = '%s' ",
- $id,
- $this->blog
- ));
- $this->con->unlock();
- $this->trigger();
- } catch (Exception $e) {
- $this->con->unlock();
-
- throw $e;
- }
-
- # --BEHAVIOR-- zoneclearFeedServerAfterUpdFeed
- dcCore::app()->callBehavior('zoneclearFeedServerAfterUpdFeed', $cur, $id);
- }
-
- /**
- * Add feed record.
- *
- * @param cursor $cur cursor instance
- */
- public function addFeed(cursor $cur)
- {
- $this->con->writeLock($this->table);
-
- try {
- $cur->feed_id = $this->getNextId();
- $cur->blog_id = $this->blog;
- $cur->feed_creadt = date('Y-m-d H:i:s');
- $cur->feed_upddt = date('Y-m-d H:i:s');
-
- //add getFeedCursor here
-
- $cur->insert();
- $this->con->unlock();
- $this->trigger();
- } catch (Exception $e) {
- $this->con->unlock();
-
- throw $e;
- }
-
- # --BEHAVIOR-- zoneclearFeedServerAfterAddFeed
- dcCore::app()->callBehavior('zoneclearFeedServerAfterAddFeed', $cur);
-
- return $cur->feed_id;
- }
-
- /**
- * Quick enable / disable feed.
- *
- * @param integer $id Feed Id
- * @param boolean $enable Enable or disable feed
- * @param integer $time Force update time
- */
- public function enableFeed($id, $enable = true, $time = null)
- {
- try {
- $id = (int) $id;
-
- if ($id < 1) {
- throw new Exception(__('No such ID'));
- }
-
- $cur = $this->openCursor();
- $this->con->writeLock($this->table);
-
- $cur->feed_upddt = date('Y-m-d H:i:s');
- $cur->feed_status = (int) $enable;
- if (null !== $time) {
- $cur->feed_upd_last = (int) $time;
- }
-
- $cur->update(sprintf(
- "WHERE feed_id = %s AND blog_id = '%s' ",
- $id,
- $this->blog
- ));
- $this->con->unlock();
- $this->trigger();
- } catch (Exception $e) {
- $this->con->unlock();
-
- throw $e;
- }
-
- # --BEHAVIOR-- zoneclearFeedServerAfterEnableFeed
- dcCore::app()->callBehavior('zoneclearFeedServerAfterEnableFeed', $id, $enable, $time);
- }
-
- #
- /**
- * Delete record (this not deletes post).
- *
- * @param integer $id Feed Id
- */
- public function delFeed($id)
- {
- $id = (int) $id;
-
- if ($id < 1) {
- throw new Exception(__('No such ID'));
- }
-
- # --BEHAVIOR-- zoneclearFeedServerBeforeDelFeed
- dcCore::app()->callBehavior('zoneclearFeedServerBeforeDelFeed', $id);
-
- $this->con->execute(sprintf(
- "DELETE FROM %s WHERE feed_id = %s AND blog_id = '%s' ",
- $this->table,
- $id,
- $this->blog
- ));
- $this->trigger();
- }
-
- /**
- * Get related posts.
- *
- * @param array $params Query params
- * @param boolean $count_only Return only result count
- * @return null|dcRecord record instance
- */
- public function getPostsByFeed($params = [], $count_only = false)
- {
- if (!isset($params['feed_id'])) {
- return null;
- }
-
- $sql = new dcSelectStatement();
- $sql->join(
- (new dcJoinStatement())
- ->type('LEFT')
- ->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' F')
- ->on('P.post_id = F.post_id')
- ->statement()
- );
-
- $params['sql'] = "AND P.blog_id = '" . $this->blog . "' " .
- "AND F.meta_type = 'zoneclearfeed_id' " .
- "AND F.meta_id = '" . $this->con->escape($params['feed_id']) . "' ";
-
- unset($params['feed_id']);
-
- return dcCore::app()->blog->getPosts($params, $count_only, $sql);
- }
-
- /**
- * Get feed record.
- *
- * @param array $params Query params
- * @param boolean $count_only Return only result count
- * @return dcRecord record instance
- */
- public function getFeeds($params = [], $count_only = false)
- {
- if ($count_only) {
- $strReq = 'SELECT count(Z.feed_id) ';
- } else {
- $content_req = '';
- if (!empty($params['columns']) && is_array($params['columns'])) {
- $content_req .= implode(', ', $params['columns']) . ', ';
- }
-
- $strReq = 'SELECT Z.feed_id, Z.feed_creadt, Z.feed_upddt, Z.feed_type, ' .
- 'Z.blog_id, Z.cat_id, ' .
- 'Z.feed_upd_int, Z.feed_upd_last, Z.feed_status, ' .
- $content_req .
- 'LOWER(Z.feed_name) as lowername, Z.feed_name, Z.feed_desc, ' .
- 'Z.feed_url, Z.feed_feed, Z.feed_get_tags, ' .
- 'Z.feed_tags, Z.feed_owner, Z.feed_tweeter, Z.feed_lang, ' .
- 'Z.feed_nb_out, Z.feed_nb_in, ' .
- 'C.cat_title, C.cat_url, C.cat_desc ';
- }
-
- $strReq .= 'FROM ' . $this->table . ' Z ' .
- 'LEFT OUTER JOIN ' . dcCore::app()->prefix . dcCategories::CATEGORY_TABLE_NAME . ' C ON Z.cat_id = C.cat_id ';
-
- if (!empty($params['from'])) {
- $strReq .= $params['from'] . ' ';
- }
-
- $strReq .= "WHERE Z.blog_id = '" . $this->blog . "' ";
-
- if (isset($params['feed_type'])) {
- $strReq .= "AND Z.feed_type = '" . $this->con->escape($params['type']) . "' ";
- } else {
- $strReq .= "AND Z.feed_type = 'feed' ";
- }
-
- if (!empty($params['feed_id'])) {
- if (is_array($params['feed_id'])) {
- array_walk($params['feed_id'], function (&$v, $k) { if ($v !== null) { $v = (int) $v; }});
- } else {
- $params['feed_id'] = [(int) $params['feed_id']];
- }
- $strReq .= 'AND Z.feed_id ' . $this->con->in($params['feed_id']);
- }
-
- if (isset($params['feed_feed'])) {
- $strReq .= "AND Z.feed_feed = '" . $this->con->escape($params['feed_feed']) . "' ";
- }
- if (isset($params['feed_url'])) {
- $strReq .= "AND Z.feed_url = '" . $this->con->escape($params['feed_url']) . "' ";
- }
- if (isset($params['feed_status'])) {
- $strReq .= 'AND Z.feed_status = ' . ((int) $params['feed_status']) . ' ';
- }
-
- if (!empty($params['q'])) {
- $q = $this->con->escape(str_replace('*', '%', strtolower($params['q'])));
- $strReq .= "AND LOWER(Z.feed_name) LIKE '" . $q . "' ";
- }
-
- if (!empty($params['sql'])) {
- $strReq .= $params['sql'] . ' ';
- }
-
- if (!$count_only) {
- if (!empty($params['order'])) {
- $strReq .= 'ORDER BY ' . $this->con->escape($params['order']) . ' ';
- } else {
- $strReq .= 'ORDER BY Z.feed_upddt DESC ';
- }
- }
-
- if (!$count_only && !empty($params['limit'])) {
- $strReq .= $this->con->limit($params['limit']);
- }
-
- $rs = $this->con->select($strReq);
-
- return $rs;
- }
-
- /**
- * Get next table id.
- *
- * @return integer Next id
- */
- private function getNextId()
- {
- return $this->con->select(
- 'SELECT MAX(feed_id) FROM ' . $this->table
- )->f(0) + 1;
- }
-
- /**
- * Lock a file to see if an update is ongoing.
- *
- * @return boolean True if file is locked
- */
- public function lockUpdate()
- {
- try {
- # Need flock function
- if (!function_exists('flock')) {
- throw new Exception("Can't call php function named flock");
- }
- # Cache writable ?
- if (!is_writable(DC_TPL_CACHE)) {
- throw new Exception("Can't write in cache fodler");
- }
- # Set file path
- $f_md5 = md5($this->blog);
- $cached_file = sprintf(
- '%s/%s/%s/%s/%s.txt',
- DC_TPL_CACHE,
- 'periodical',
- substr($f_md5, 0, 2),
- substr($f_md5, 2, 2),
- $f_md5
- );
- # Real path
- $cached_file = path::real($cached_file, false);
- # Make dir
- if (!is_dir(dirname($cached_file))) {
- files::makeDir(dirname($cached_file), true);
- }
- # Make file
- if (!file_exists($cached_file)) {
- !$fp = @fopen($cached_file, 'w');
- if ($fp === false) {
- throw new Exception("Can't create file");
- }
- fwrite($fp, '1', strlen('1'));
- fclose($fp);
- }
- # Open file
- if (!($fp = @fopen($cached_file, 'r+'))) {
- throw new Exception("Can't open file");
- }
- # Lock file
- if (!flock($fp, LOCK_EX)) {
- throw new Exception("Can't lock file");
- }
- $this->lock = $fp;
-
- return true;
- } catch (Exception $e) {
- throw $e;
- }
-
- return false;
- }
-
- /**
- * Unlock file of update process.
- */
- public function unlockUpdate()
- {
- @fclose($this->lock);
- $this->lock = null;
- }
-
- /**
- * Check and add/update post related to record if needed.
- *
- * @param integer $id Feed Id
- * @param boolean $throw Throw exception or end silently
- * @return boolean True if process succeed
- */
- public function checkFeedsUpdate($id = null, $throw = false)
- {
- $s = dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)));
-
- # Not configured
- if (!$s->active || !$s->user) {
- return false;
- }
-
- # Limit to one update at a time
- try {
- $this->lockUpdate();
- } catch (Exception $e) {
- if ($throw) {
- throw $e;
- }
-
- return false;
- }
-
- dt::setTZ(dcCore::app()->blog->settings->system->blog_timezone);
- $time = time();
-
- # All feeds or only one (from admin)
- $f = !$id ?
- $this->getFeeds(['feed_status' => 1, 'order' => 'feed_upd_last ASC']) :
- $this->getFeeds(['feed_id' => $id]);
-
- # No feed
- if ($f->isEmpty()) {
- return false;
- }
-
- $enabled = false;
- $updates = false;
- $loop_mem = [];
-
- $limit = abs((int) $s->update_limit);
- if ($limit < 1) {
- $limit = 10;
- }
- $i = 0;
-
- $cur_post = $this->con->openCursor(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME);
- $cur_meta = $this->con->openCursor(dcCore::app()->prefix . dcMeta::META_TABLE_NAME);
-
- while ($f->fetch()) {
- # Check if feed need update
- if ($id
- || $i < $limit && $f->feed_status == 1 && ($time > $f->feed_upd_last + $f->feed_upd_int)
- ) {
- if (!$enabled) {
- # Set feeds user
- $this->enableUser(true);
- $enabled = true;
- }
- $i++;
- $feed = self::readFeed($f->feed_feed);
-
- # Nothing to parse
- if (!$feed) {
- # Keep active empty feed or disable it ?
- if (!$s->keep_empty_feed) {
- $this->enableFeed($f->feed_id, false);
- } else {
- # Set update time of this feed
- $this->enableFeed($f->feed_id, true, $time);
- }
- $i++;
-
- # Not updated since last visit
- } elseif (!$id
- && '' != $feed->pubdate
- && strtotime($feed->pubdate) < $f->feed_upd_last
- ) {
- # Set update time of this feed
- $this->enableFeed($f->feed_id, true, $time);
- $i++;
- } else {
- # Set update time of this feed
- $this->enableFeed($f->feed_id, $f->feed_status, $time);
-
- $this->con->begin();
-
- foreach ($feed->items as $item) {
- $item_TS = $item->TS ? $item->TS : $time;
-
- // I found that mercurial atom feed did not repect standard
- $item_link = @$item->link;
- if (!$item_link) {
- $item_link = @$item->guid;
- }
- # Unknow feed item link
- if (!$item_link) {
- continue;
- }
-
- $item_link = $this->con->escape($item_link);
- $is_new_published_entry = false;
-
- # Not updated since last visit
- if (!$id && $item_TS < $f->feed_upd_last) {
- continue;
- }
-
- # Fix loop twin
- if (in_array($item_link, $loop_mem)) {
- continue;
- }
- $loop_mem[] = $item_link;
-
- # Check if entry exists
- $old_post = $this->con->select(
- 'SELECT P.post_id, P.post_status ' .
- 'FROM ' . dcCore::app()->prefix . dcBlog::POST_TABLE_NAME . ' P ' .
- 'INNER JOIN ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' M ' .
- 'ON P.post_id = M.post_id ' .
- "WHERE blog_id='" . $this->blog . "' " .
- "AND meta_type = 'zoneclearfeed_url' " .
- "AND meta_id = '" . $item_link . "' "
- );
-
- # Prepare entry cursor
- $cur_post->clean();
- $cur_post->post_dt = date('Y-m-d H:i:s', $item_TS);
- if ($f->cat_id) {
- $cur_post->cat_id = $f->cat_id;
- }
- $post_content = $item->content ? $item->content : $item->description;
- $cur_post->post_format = 'xhtml';
- $cur_post->post_content = html::absoluteURLs($post_content, $feed->link);
- $cur_post->post_title = $item->title ? $item->title : text::cutString(html::clean($cur_post->post_content), 60);
- $creator = $item->creator ? $item->creator : $f->feed_owner;
-
- try {
- # Create entry
- if ($old_post->isEmpty()) {
- # Post
- $cur_post->user_id = dcCore::app()->auth->userID();
- $cur_post->post_format = 'xhtml';
- $cur_post->post_status = (int) $s->post_status_new;
- $cur_post->post_open_comment = 0;
- $cur_post->post_open_tb = 0;
-
- # --BEHAVIOR-- zoneclearFeedServerBeforePostCreate
- dcCore::app()->callBehavior(
- 'zoneclearFeedServerBeforePostCreate',
- $cur_post
- );
-
- $post_id = dcCore::app()->auth->sudo(
- [dcCore::app()->blog, 'addPost'],
- $cur_post
- );
-
- # --BEHAVIOR-- zoneclearFeedServerAfterPostCreate
- dcCore::app()->callBehavior(
- 'zoneclearFeedServerAfterPostCreate',
- $cur_post,
- $post_id
- );
-
- # Auto tweet new post
- if ($cur_post->post_status == 1) {
- $is_new_published_entry = true;
- }
-
- # Update entry
- } else {
- $post_id = $old_post->post_id;
-
- # --BEHAVIOR-- zoneclearFeedServerBeforePostUpdate
- dcCore::app()->callBehavior(
- 'zoneclearFeedServerBeforePostUpdate',
- $cur_post,
- $post_id
- );
-
- dcCore::app()->auth->sudo(
- [dcCore::app()->blog, 'updPost'],
- $post_id,
- $cur_post
- );
-
- # Quick delete old meta
- $this->con->execute(
- 'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
- 'WHERE post_id = ' . $post_id . ' ' .
- "AND meta_type LIKE 'zoneclearfeed_%' "
- );
-
- # Delete old tags
- dcCore::app()->auth->sudo(
- [dcCore::app()->meta, 'delPostMeta'],
- $post_id,
- 'tag'
- );
-
- # --BEHAVIOR-- zoneclearFeedServerAfterPostUpdate
- dcCore::app()->callBehavior(
- 'zoneclearFeedServerAfterPostUpdate',
- $cur_post,
- $post_id
- );
- }
-
- # Quick add new meta
- $meta = new ArrayObject();
- $meta->tweeter = $f->feed_tweeter;
-
- $cur_meta->clean();
- $cur_meta->post_id = $post_id;
- $cur_meta->meta_type = 'zoneclearfeed_url';
- $cur_meta->meta_id = $meta->url = $item_link;
- $cur_meta->insert();
-
- $cur_meta->clean();
- $cur_meta->post_id = $post_id;
- $cur_meta->meta_type = 'zoneclearfeed_author';
- $cur_meta->meta_id = $meta->author = $creator;
- $cur_meta->insert();
-
- $cur_meta->clean();
- $cur_meta->post_id = $post_id;
- $cur_meta->meta_type = 'zoneclearfeed_site';
- $cur_meta->meta_id = $meta->site = $f->feed_url;
- $cur_meta->insert();
-
- $cur_meta->clean();
- $cur_meta->post_id = $post_id;
- $cur_meta->meta_type = 'zoneclearfeed_sitename';
- $cur_meta->meta_id = $meta->sitename = $f->feed_name;
- $cur_meta->insert();
-
- $cur_meta->clean();
- $cur_meta->post_id = $post_id;
- $cur_meta->meta_type = 'zoneclearfeed_id';
- $cur_meta->meta_id = $meta->id = $f->feed_id;
- $cur_meta->insert();
-
- # Add new tags
- $tags = dcCore::app()->meta->splitMetaValues($f->feed_tags);
- if ($f->feed_get_tags) {
- # Some feed subjects contains more than one tag
- foreach ($item->subject as $subjects) {
- $tmp = dcCore::app()->meta->splitMetaValues($subjects);
- $tags = array_merge($tags, $tmp);
- }
- $tags = array_unique($tags);
- }
- $formated_tags = [];
- foreach ($tags as $tag) {
- # Change tags case
- switch ((int) $s->tag_case) {
- case 3: $tag = strtoupper($tag);
-
- break;
- case 2: $tag = strtolower($tag);
-
- break;
- case 1: $tag = ucfirst(strtolower($tag));
-
- break;
- default: /* do nothing */ break;
- }
- if (!in_array($tag, $formated_tags)) {
- $formated_tags[] = $tag;
- dcCore::app()->auth->sudo(
- [dcCore::app()->meta, 'delPostMeta'],
- $post_id,
- 'tag',
- dcMeta::sanitizeMetaID($tag)
- );
- dcCore::app()->auth->sudo(
- [dcCore::app()->meta, 'setPostMeta'],
- $post_id,
- 'tag',
- dcMeta::sanitizeMetaID($tag)
- );
- }
- }
- $meta->tags = $formated_tags;
-
- # --BEHAVIOR-- zoneclearFeedServerAfterFeedUpdate
- dcCore::app()->callBehavior(
- 'zoneclearFeedServerAfterFeedUpdate',
- $is_new_published_entry,
- $cur_post,
- $meta
- );
- } catch (Exception $e) {
- $this->con->rollback();
- $this->enableUser(false);
- $this->unlockUpdate();
-
- throw $e;
- }
- $updates = true;
- }
- $this->con->commit();
- }
- }
- }
- if ($enabled) {
- $this->enableUser(false);
- }
- $this->unlockUpdate();
-
- return true;
- }
-
- /**
- * Set permission to update post table.
- *
- * @param boolean $enable Enable or disable perm
- */
- public function enableUser($enable = false)
- {
- # Enable
- if ($enable) {
- // backup current user
- $this->user = dcCore::app()->auth->userID();
- // set zcfs posts user
- if (!dcCore::app()->auth->checkUser((string) dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->user)) {
- throw new Exception('Unable to set user');
- }
- # Disable
- } else {
- dcCore::app()->auth = null;
- dcCore::app()->auth = new dcAuth();
- // restore current user
- dcCore::app()->auth->checkUser($this->user ?? '');
- }
- }
-
- /**
- * Read and parse external feeds.
- *
- * @param string $f Feed URL
- * @return feedParser|false Parsed feed
- */
- public static function readFeed($f)
- {
- try {
- $feed_reader = new feedReader();
- $feed_reader->setCacheDir(DC_TPL_CACHE);
- $feed_reader->setTimeout(self::$nethttp_timeout);
- $feed_reader->setMaxRedirects(self::$nethttp_maxredirect);
- $feed_reader->setUserAgent(self::$nethttp_agent);
-
- return $feed_reader->parse($f);
- } catch (Exception $e) {
- return false;
- }
- }
-
- /**
- * Trigger.
- */
- private function trigger()
- {
- dcCore::app()->blog->triggerBlog();
- }
-
- /**
- * Check if an URL is well formed
- *
- * @param string $url URL
- * @return Boolean True if URL is allowed
- */
- public static function validateURL($url)
- {
- return false !== strpos($url, 'http://')
- || false !== strpos($url, 'https://');
- }
-
- /**
- * Get full URL.
- *
- * Know bugs: anchor is not well parsed.
- *
- * @param string $root Root URL
- * @param string $url An URL
- * @return string Parse URL
- */
- public static function absoluteURL($root, $url)
- {
- $host = preg_replace(
- '|^([a-z]{3,}://)(.*?)/(.*)$|',
- '$1$2',
- $root
- );
-
- $parse = parse_url($url);
-
- if (empty($parse['scheme'])) {
- if (strpos($url, '/') === 0) {
- $url = $host . $url;
- } elseif (strpos($url, '#') === 0) {
- $url = $root . $url;
- } elseif (preg_match('|/$|', $root)) {
- $url = $root . $url;
- } else {
- $url = dirname($root) . '/' . $url;
- }
- }
-
- return $url;
- }
-
- /**
- * Get list of feeds status.
- *
- * @return array List of names/values of feeds status
- */
- public static function getAllStatus()
- {
- return [
- __('Disabled') => '0',
- __('Enabled') => '1',
- ];
- }
-
- /**
- * Get list of predefined interval.
- *
- * @return array List of Name/time of intervals
- */
- public static function getAllUpdateInterval()
- {
- return [
- __('Every hour') => 3600,
- __('Every two hours') => 7200,
- __('Two times per day') => 43200,
- __('Every day') => 86400,
- __('Every two days') => 172800,
- __('Every week') => 604800,
- ];
- }
-
- /**
- * Get list of (super)admins of current blog.
- *
- * @return array List of UserCNs/UserIds
- */
- public function getAllBlogAdmins()
- {
- $admins = [];
-
- # Get super admins
- $rs = $this->con->select(
- 'SELECT user_id, user_super, user_name, user_firstname, user_displayname ' .
- 'FROM ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME) . ' ' .
- 'WHERE user_super = 1 AND user_status = 1 '
- );
-
- if (!$rs->isEmpty()) {
- while ($rs->fetch()) {
- $user_cn = dcUtils::getUserCN(
- $rs->user_id,
- $rs->user_name,
- $rs->user_firstname,
- $rs->user_displayname
- );
- $admins[$user_cn . ' (super admin)'] = $rs->user_id;
- }
- }
-
- # Get admins
- $rs = $this->con->select(
- 'SELECT U.user_id, U.user_super, U.user_name, U.user_firstname, U.user_displayname ' .
- 'FROM ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME) . ' U ' .
- 'LEFT JOIN ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::PERMISSIONS_TABLE_NAME) . ' P ' .
- 'ON U.user_id=P.user_id ' .
- 'WHERE U.user_status = 1 ' .
- "AND P.blog_id = '" . $this->blog . "' " .
- "AND P.permissions LIKE '%|admin|%' "
- );
-
- if (!$rs->isEmpty()) {
- while ($rs->fetch()) {
- $user_cn = dcUtils::getUserCN(
- $rs->user_id,
- $rs->user_name,
- $rs->user_firstname,
- $rs->user_displayname
- );
- $admins[$user_cn . ' (admin)'] = $rs->user_id;
- }
- }
-
- return $admins;
- }
-
- /**
- * Get list of urls where entries could be hacked.
- *
- * @return array List of names/types of URLs
- */
- public static function getPublicUrlTypes()
- {
- $types = [];
-
- # --BEHAVIOR-- zoneclearFeedServerPublicUrlTypes
- dcCore::app()->callBehavior('zoneclearFeedServerPublicUrlTypes', $types);
-
- $types[__('Home page')] = 'default';
- $types[__('Entries pages')] = 'post';
- $types[__('Tags pages')] = 'tag';
- $types[__('Archives pages')] = 'archive';
- $types[__('Category pages')] = 'category';
- $types[__('Entries feed')] = 'feed';
-
- return $types;
- }
-
- /**
- * Take care about plugin tweakurls (thanks Mathieu M.).
- *
- * @param cursor $cur cursor instance
- * @param integer $id Post Id
- */
- public static function tweakurlsAfterPostCreate(cursor $cur, $id)
- {
- if (version_compare(dcCore::app()->plugins->moduleInfo('tweakurls', 'version'), '0.8', '>=')) {
- $cur->post_url = tweakUrls::tweakBlogURL($cur->post_url);
- dcCore::app()->auth->sudo([dcCore::app()->blog, 'updPost'], $id, $cur);
- }
- }
-}