partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit e4049c412cf739ea217a217f3bf00b9a5d0b6929
parent f701ebb49a73d5ca4ba6aa40763894375023e3c5
Author: Felicitus <felicitus@felicitus.org>
Date:   Mon, 28 Sep 2015 17:21:03 +0200

Migrated tip of the day feature

Diffstat:
Mapp/config/config.yml | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/CoreBundle/DependencyInjection/Configuration.php | 16+++++++++++++---
Msrc/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php | 2++
Msrc/PartKeepr/FrontendBundle/Controller/IndexController.php | 3+--
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/TipOfTheDay/TipOfTheDayWindow.js | 245++++++++++++++++++++++++++++++-------------------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/User/Preferences/TipOfTheDayPreferences.js | 90+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Dsrc/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/Iframe.js | 120-------------------------------------------------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/PartKeepr.js | 12++++++++++--
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Util/Filter.js | 31+++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 2+-
Asrc/PartKeepr/TipOfTheDayBundle/Action/GetTipHistoryCollectionAction.php | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Action/MarkTipReadAction.php | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Action/MarkTipsAsUnreadAction.php | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Console/Command/SyncTipsCommand.php | 24++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/DataFixtures/TipOfTheDayLoader.php | 22++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/DependencyInjection/PartKeeprTipOfTheDayExtension.php | 26++++++++++++++++++++++++++
Msrc/PartKeepr/TipOfTheDayBundle/Entity/TipOfTheDay.php | 71-----------------------------------------------------------------------
Msrc/PartKeepr/TipOfTheDayBundle/PartKeeprTipOfTheDayBundle.php | 9+++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Resources/config/actions.xml | 24++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Resources/config/services.xml | 13+++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Services/TipOfTheDayService.php | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/TipOfTheDayBundle/Tests/TipOfTheDayTest.php | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/backend/PartKeepr/TipOfTheDay/TipOfTheDayService.php | 87-------------------------------------------------------------------------------
23 files changed, 739 insertions(+), 482 deletions(-)

diff --git a/app/config/config.yml b/app/config/config.yml @@ -943,6 +943,48 @@ services: - method: "initItemOperations" arguments: [ [ "@resource.tempfile.item_operation.get", "@resource.tempfile.item_operation.custom_get", "@resource.tempfile.item_operation.custom_get_mimetype", "@resource.tempfile.collection_operation.custom_post_webcam" ] ] + + resource.tip_of_the_day.item_operation.get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: [ "@resource.tip_of_the_day", "GET" ] + + resource.tip_of_the_day.item_operation.mark_read: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: + - "@resource.tip_of_the_day" + - "PUT" + - "/tip_of_the_days/{id}/markTipRead" + - "partkeepr.tip_of_the_day.mark_read" + - "TipMarkRead" + + + resource.tip_of_the_day.collection_operation.get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: [ "@resource.tip_of_the_day", "GET" ] + + resource.tip_of_the_day.collection_operation.post: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: [ "@resource.tip_of_the_day", "POST" ] + + resource.tip_of_the_day.collection_operation.mark_all_unread: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: + - "@resource.tip_of_the_day" + - [ "POST" ] + - "/tip_of_the_days/markAllTipsAsUnread" + - "partkeepr.tip_of_the_day.mark_all_unread" + - "TipMarkAllUnrad" + resource.tip_of_the_day: parent: "api.resource" arguments: [ "PartKeepr\\TipOfTheDayBundle\\Entity\\TipOfTheDay" ] @@ -952,10 +994,25 @@ services: arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] - method: "initNormalizationContext" arguments: [ { groups: [ "default" ] } ] + - method: "initCollectionOperations" + arguments: [ [ "@resource.tip_of_the_day.collection_operation.get", "@resource.tip_of_the_day.collection_operation.post", "@resource.tip_of_the_day.collection_operation.mark_all_unread" ] ] + - method: "initItemOperations" + arguments: [ [ "@resource.tip_of_the_day.item_operation.get", "@resource.tip_of_the_day.item_operation.mark_read" ] ] - method: "initDenormalizationContext" arguments: - { groups: [ "default" ] } + resource.tip_of_the_day_history.collection_operation.custom_get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: + - "@resource.tip_of_the_day_history" + - [ "GET" ] + - "/tip_of_the_day_histories" + - "partkeepr.tip_of_the_day_history.collection_get" + - "TipHistoriesGet" + resource.tip_of_the_day_history: parent: "api.resource" arguments: [ "PartKeepr\\TipOfTheDayBundle\\Entity\\TipOfTheDayHistory" ] @@ -963,6 +1020,8 @@ services: calls: - method: "initFilters" arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initCollectionOperations" + arguments: [ [ "@resource.tip_of_the_day_history.collection_operation.custom_get" ] ] - method: "initNormalizationContext" arguments: [ { groups: [ "default" ] } ] - method: "initDenormalizationContext" diff --git a/src/PartKeepr/CoreBundle/DependencyInjection/Configuration.php b/src/PartKeepr/CoreBundle/DependencyInjection/Configuration.php @@ -21,20 +21,30 @@ class Configuration implements ConfigurationInterface ->defaultValue('PartKeepr.Auth.WSSEAuthenticationProvider') ->info('The authentication provider for the frontend') ->end() + ->scalarNode('tip_of_the_day_uri') + ->cannotBeEmpty() + ->defaultValue('http://partkeepr.org/tips/%s') + ->info('The URI where tips are loaded from') + ->end() + ->scalarNode('tip_of_the_day_list') + ->cannotBeEmpty() + ->defaultValue('http://partkeepr.org/tips.json') + ->info('The URI from where the tip database is loaded') + ->end() ->scalarNode('image_cache_directory') ->cannotBeEmpty() ->isRequired() ->info('The image cache directory') ->end() - ->arrayNode('directories') + ->arrayNode('directories') ->prototype('scalar') ->end() ->end() ->booleanNode('cronjob_check') ->defaultTrue() ->info('Whether the system should check if cronjobs are running or not') - ->end() - ->end(); + ->end() + ->end(); return $treeBuilder; } diff --git a/src/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php b/src/PartKeepr/CoreBundle/DependencyInjection/PartKeeprCoreExtension.php @@ -25,6 +25,8 @@ class PartKeeprCoreExtension extends Extension $container->setParameter('partkeepr.cronjob_check', $config['cronjob_check']); $container->setParameter('partkeepr.image_cache_directory', $config['image_cache_directory']); $container->setParameter('partkeepr.authentication_provider', $config['authentication_provider']); + $container->setParameter('partkeepr.tip_of_the_day_uri', $config['tip_of_the_day_uri']); + $container->setParameter('partkeepr.tip_of_the_day_list', $config['tip_of_the_day_list']); foreach ($config["directories"] as $key => $value) { $container->setParameter("partkeepr.directories.".$key, $value); diff --git a/src/PartKeepr/FrontendBundle/Controller/IndexController.php b/src/PartKeepr/FrontendBundle/Controller/IndexController.php @@ -4,11 +4,9 @@ namespace PartKeepr\FrontendBundle\Controller; use Doctrine\Common\Version as DoctrineCommonVersion; use Doctrine\DBAL\Version as DBALVersion; -use Doctrine\ORM\NoResultException; use Doctrine\ORM\Version as ORMVersion; use PartKeepr\AuthBundle\Entity\User; use PartKeepr\PartKeepr; -use PartKeepr\Session\SessionManager; use PartKeepr\Util\Configuration; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Finder\Finder; @@ -65,6 +63,7 @@ class IndexController extends Controller } $aParameters["authentication_provider"] = $this->getParameter("partkeepr.authentication_provider"); + $aParameters["tip_of_the_day_uri"] = $this->getParameter("partkeepr.tip_of_the_day_uri"); $renderParams = array(); $renderParams["debug_all"] = Configuration::getOption("partkeepr.frontend.debug_all", false); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/TipOfTheDay/TipOfTheDayWindow.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/TipOfTheDay/TipOfTheDayWindow.js @@ -46,7 +46,7 @@ Ext.define("PartKeepr.TipOfTheDayWindow", { this.tipHistoryStore = Ext.data.StoreManager.lookup('TipOfTheDayHistoryStore'); // Set the tip display iframe and add it to the items - this.tipDisplay = Ext.create("Ext.ux.SimpleIFrame", { + this.tipDisplay = Ext.create("Ext.ux.IFrame", { border: false }); @@ -103,18 +103,72 @@ Ext.define("PartKeepr.TipOfTheDayWindow", { ]; // Auto-load the next unread tip on window display - this.on("show", this.displayNextTip, this); + this.updateFilter(); + this.currentTip = this.tipStore.getAt(0); + this.on("show", this.displayTip, this); // Window destroy handler this.on("destroy", this.onDestroy, this); this.callParent(); }, /** + * Displays the previous tip + */ + displayPreviousTip: function () + { + var idx = this.tipStore.indexOf(this.currentTip); + this.currentTip = this.tipStore.getAt(idx - 1); + + if (this.currentTip === null) { + this.currentTip = this.tipStore.getAt(0); + } + this.displayTip(this.currentTip); + }, + /** + * Displays the next tip + */ + displayNextTip: function () + { + var idx = this.tipStore.indexOf(this.currentTip); + this.currentTip = this.tipStore.getAt(idx + 1); + + if (this.currentTip === null) { + this.currentTip = this.tipStore.getAt(0); + } + this.displayTip(this.currentTip); + }, + /** + * Updates the filter for the tip store to exclude read tips. + */ + updateFilter: function () + { + this.tipStore.clearFilter(); + + if (this.displayReadTipsCheckbox.getValue() === true) { + return; + } + var filterItems = []; + + this.tipHistoryStore.each(function (record) + { + filterItems.push(record.get("name")); + }); + + var tipFilter = Ext.create("PartKeepr.util.Filter", { + property: "name", + operator: "notin", + value: filterItems + }); + + this.tipStore.addFilter(tipFilter); + }, + /** * If the "show read tips" checkbox was clicked, update the buttons * to reflect the tip navigation. */ showReadTipsHandler: function () { + this.updateFilter(); this.updateButtons(this.currentTip); }, /** @@ -144,21 +198,30 @@ Ext.define("PartKeepr.TipOfTheDayWindow", { * Displays a specific tip of the day. * @param record The record which contains the information regarding the tip */ - displayTip: function (record) + displayTip: function () { + if (!this.currentTip) { + return; + } + // Cancel the old read timer this.cancelReadTimer(); // Update buttons to reflect position - this.updateButtons(record); + this.updateButtons(this.currentTip); // Set the title to the tip name - this.setTitle(this.titleTemplate + ": " + record.get("name")); + this.setTitle(this.titleTemplate + ": " + this.currentTip.get("name")); // Set iframe to the tip url - this.tipDisplay.setSrc(record.get("url")); + this.tipDisplay.load( + sprintf(PartKeepr.getApplication().getParameter("tip_of_the_day_uri"), this.currentTip.get("name"))); // Fire up delayed task to mark the tip as read + if (this.markAsReadTask) { + this.markAsReadTask.cancel(); + } + this.markAsReadTask = new Ext.util.DelayedTask(this.markTipRead, this); this.markAsReadTask.delay(5000); @@ -171,179 +234,55 @@ Ext.define("PartKeepr.TipOfTheDayWindow", { */ updateButtons: function (record) { - if (this.displayReadTipsCheckbox.getValue() === true) { - if (this.tipStore.indexOf(record) > 0) { - this.previousButton.enable(); - } else { - this.previousButton.disable(); - } - - if (this.tipStore.indexOf(record) === this.tipStore.getTotalCount() - 1) { - this.nextButton.disable(); - } else { - this.nextButton.enable(); - } + if (this.tipStore.indexOf(record) > 0) { + this.previousButton.enable(); } else { - if (this.tipStore.indexOf(record) > this.getFirstUnreadTip()) { - this.previousButton.enable(); - } else { - this.previousButton.disable(); - } - - - if (this.tipStore.indexOf(record) >= this.getLastUnreadTip()) { - this.nextButton.disable(); - } else { - this.nextButton.enable(); - } + this.previousButton.disable(); } - }, - /** - * Returns the index of the first unread tip, or null if there's no unread tip. - * @returns int The index of the first unread tip, or null - */ - getFirstUnreadTip: function () - { - for (var i = 0; i < this.tipStore.getTotalCount(); i++) { - if (this.tipStore.getAt(i).get("read") === false) { - return i; - } - } - - return null; - }, - /** - * Returns the index of the last unread tip, or null if there's no unread tip. - * @returns int The index of the last unread tip, or null - */ - getLastUnreadTip: function () - { - for (var i = this.tipStore.getTotalCount() - 1; i > -1; i--) { - if (this.tipStore.getAt(i).get("read") === false) { - return i; - } + if (this.tipStore.indexOf(record) === this.tipStore.getCount() - 1) { + this.nextButton.disable(); + } else { + this.nextButton.enable(); } - return null; - }, - isTipRead: function ( - tip - ) - { - /*var filter = Ext.create("Ext.util.Filter", { - property: - }), - this.tipHistoryStore.*/ - }, /** * Marks the current tip as read. Commits the information to the server. */ markTipRead: function () { - this.currentTip.set("read", true); - this.currentTip.commit(); - - var call = new PartKeepr.ServiceCall("TipOfTheDay", "markTipAsRead"); - call.setLoadMessage(sprintf(i18n("Marking tip %s as read..."), this.currentTip.get("name"))); - call.setParameter("name", this.currentTip.get("name")); - call.doCall(); + this.currentTip.callPutAction("markTipRead", {}, Ext.bind(this.onMarkTipRead, this)); }, /** - * Displays the next tip + * Callback for when the markTipRead action has been completed. Re-loads the history store */ - displayNextTip: function () + onMarkTipRead: function () { - this.retrieveTip("ASC"); + this.tipHistoryStore.load({ + scope: this, + callback: this.onHistoryStoreLoaded + }); }, /** - * Displays the previous tip + * Callback for when the history store has been loaded. Updates the filter */ - displayPreviousTip: function () + onHistoryStoreLoaded: function () { - this.retrieveTip("DESC"); + this.updateFilter(); }, /** - * Displays the next or previous tip. + * Returns if there are tips in the tip database which aren't read. * - * @param dir string Either "ASC" or "DESC", which denotes the direction to search for the next tip + * @return {Boolean} True if there are tips available, false otherwise */ - retrieveTip: function (dir) + hasTips: function () { - var startIdx = -1, record = null; - - if (this.currentTip) { - startIdx = this.tipStore.indexOf(this.currentTip); - } - - if (dir === "ASC") { - record = this.extractNextTip(startIdx); + if (this.tipStore.count() > 0) { + return true; } else { - record = this.extractPreviousTip(startIdx); + return false; } - if (record) { - this.currentTip = record; - this.displayTip(record); - } - }, - /** - * Returns the record with the next tip - * @param startIdx The index to start searching from - * @returns record The record with the next tip - */ - extractNextTip: function (startIdx) - { - var record = null, foundRecord = null; - if (this.displayReadTipsCheckbox.getValue() === true) { - var tmpIdx = startIdx + 1; - if (tmpIdx > this.tipStore.getTotalCount() - 1) { - tmpIdx = this.tipStore.getTotalCount() - 1; - } - - foundRecord = this.tipStore.getAt(tmpIdx); - } else { - for (var i = startIdx + 1; i < this.tipStore.getTotalCount(); i++) { - record = this.tipStore.getAt(i); - if (record.get("read") === false) { - foundRecord = record; - break; - } - } - } - - return foundRecord; - }, - /** - * Returns the record with the previous tip - * @param startIdx The index to start searching from - * @returns record The record with the previous tip - */ - extractPreviousTip: function (startIdx) - { - var record = null, foundRecord = null; - if (this.displayReadTipsCheckbox.getValue() === true) { - var tmpIdx = startIdx - 1; - if (tmpIdx < 0) { - tmpIdx = 0; - } - - foundRecord = this.tipStore.getAt(tmpIdx); - } else { - for (var i = startIdx - 1; i > -1; i--) { - record = this.tipStore.getAt(i); - - if (record.get("read") === false) { - foundRecord = record; - break; - } - } - } - - - return foundRecord; } - - }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/User/Preferences/TipOfTheDayPreferences.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/User/Preferences/TipOfTheDayPreferences.js @@ -1,47 +1,51 @@ Ext.define('PartKeepr.TipOfTheDayPreferencesPanel', { - extend: 'Ext.form.FormPanel', - title: i18n("Tip of the Day"), - bodyStyle: 'background:#DBDBDB;padding: 10px;', - initComponent: function () { - this.displayTipsOnLoginCheckbox = Ext.create("Ext.form.field.Checkbox", { - boxLabel: i18n("Display tips on login"), - handler: Ext.bind(this.showTipsHandler, this) - }); - - if (PartKeepr.getApplication().getUserPreference("partkeepr.tipoftheday.showtips") == "false") { - this.displayTipsOnLoginCheckbox.setValue(false); - } else { - this.displayTipsOnLoginCheckbox.setValue(true); - } - - - this.resetTipsButton = Ext.create("Ext.button.Button", { - text: i18n("Mark all tips unread"), - handler: this.onMarkAllTipsUnreadClick, - scope: this - }); - - this.items = [ this.displayTipsOnLoginCheckbox, - this.resetTipsButton ]; - - this.callParent(); + extend: 'Ext.form.FormPanel', + title: i18n("Tip of the Day"), + bodyStyle: 'background:#DBDBDB;padding: 10px;', + initComponent: function () + { + this.displayTipsOnLoginCheckbox = Ext.create("Ext.form.field.Checkbox", { + boxLabel: i18n("Display tips on login"), + handler: Ext.bind(this.showTipsHandler, this) + }); + + if (PartKeepr.getApplication().getUserPreference("partkeepr.tipoftheday.showtips") == "false") { + this.displayTipsOnLoginCheckbox.setValue(false); + } else { + this.displayTipsOnLoginCheckbox.setValue(true); + } + + + this.resetTipsButton = Ext.create("Ext.button.Button", { + text: i18n("Mark all tips unread"), + handler: this.onMarkAllTipsUnreadClick, + scope: this + }); + + this.items = [ + this.displayTipsOnLoginCheckbox, + this.resetTipsButton + ]; + + this.callParent(); }, /** - * Handler when the "show tips" checkbox was clicked. - */ - showTipsHandler: function (checkbox, checked) { - PartKeepr.getApplication().setUserPreference("partkeepr.tipoftheday.showtips", checked); - }, - /** - * Marks all tips as unread - */ - onMarkAllTipsUnreadClick: function () { - var call = new PartKeepr.ServiceCall("TipOfTheDay", "markAllTipsAsUnread"); - call.setLoadMessage(i18n("Marking all tips as unerad...")); - call.setHandler(function () { - var msg = i18n("All tips have been marked as unread"); - Ext.Msg.alert(msg, msg); - }); - call.doCall(); - } + * Handler when the "show tips" checkbox was clicked. + */ + showTipsHandler: function (checkbox, checked) + { + PartKeepr.getApplication().setUserPreference("partkeepr.tipoftheday.showtips", checked); + }, + /** + * Marks all tips as unread + */ + onMarkAllTipsUnreadClick: function () + { + PartKeepr.TipOfTheDayBundle.Entity.TipOfTheDay.callPostCollectionAction("markAllTipsAsUnread", {}, function () + { + var msg = i18n("All tips have been marked as unread"); + Ext.Msg.alert(msg, msg); + } + ); + } }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/Iframe.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Ext.ux/Iframe.js @@ -1,119 +0,0 @@ -// vim: sw=2:ts=2:nu:nospell:fdc=2:expandtab -/** -* @class Ext.ux.SimpleIFrame -* @extends Ext.Panel -* -* A simple ExtJS 4 implementaton of an iframe providing basic functionality. -* For example: -* -* var panel=Ext.create('Ext.ux.SimpleIFrame', { -* border: false, -* src: 'http://localhost' -* }); -* panel.setSrc('http://www.sencha.com'); -* panel.reset(); -* panel.reload(); -* panel.getSrc(); -* panel.update('<div><b>Some Content....</b></div>'); -* panel.destroy(); -* -* @author Conor Armstrong -* @copyright (c) 2011 Conor Armstrong -* @date 12 April 2011 -* @version 0.1 -* -* @license Ext.ux.SimpleIFrame.js is licensed under the terms of the Open Source -* LGPL 3.0 license. Commercial use is permitted to the extent that the -* code/component(s) do NOT become part of another Open Source or Commercially -* licensed development library or toolkit without explicit permission. -* -* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html" -* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p> -* -*/ - -Ext.require([ - 'Ext.panel.*' -]); - -Ext.define('Ext.ux.SimpleIFrame', { - extend: 'Ext.Panel', - alias: 'widget.simpleiframe', - src: 'about:blank', - loadingText: 'Loading ...', - initComponent: function(){ - this.updateHTML(); - this.callParent(arguments); - }, - updateHTML: function() { - this.html='<iframe id="iframe-'+this.id+'"'+ - ' style="overflow:auto;width:100%;height:100%;"'+ - ' frameborder="0" '+ - ' src="'+this.src+'"'+ - '></iframe>'; - }, - reload: function() { - this.setSrc(this.src); - }, - reset: function() { - var iframe=this.getDOM(); - var iframeParent=iframe.parentNode; - if (iframe && iframeParent) { - iframe.src='about:blank'; - iframe.parentNode.removeChild(iframe); - } - - iframe=document.createElement('iframe'); - iframe.frameBorder=0; - iframe.src=this.src; - iframe.id='iframe-'+this.id; - iframe.style.overflow='auto'; - iframe.style.width='100%'; - iframe.style.height='100%'; - iframeParent.appendChild(iframe); - }, - setSrc: function(src, loadingText) { - this.src=src; - var iframe=this.getDOM(); - if (iframe) { - iframe.src=src; - } - }, - getSrc: function() { - return this.src; - }, - getDOM: function() { - return document.getElementById('iframe-'+this.id); - }, - getDocument: function() { - var iframe=this.getDOM(); - iframe = (iframe.contentWindow) ? iframe.contentWindow : (iframe.contentDocument.document) ? iframe.contentDocument.document : iframe.contentDocument; - return iframe.document; - }, - destroy: function() { - var iframe=this.getDOM(); - if (iframe && iframe.parentNode) { - iframe.src='about:blank'; - iframe.parentNode.removeChild(iframe); - } - this.callParent(arguments); - }, - update: function(content) { - var doc; - - this.setSrc('about:blank'); - try { - doc=this.getDocument(); - doc.open(); - doc.write(content); - doc.close(); - } catch(err) { - // reset if any permission issues - this.reset(); - doc=this.getDocument(); - doc.open(); - doc.write(content); - doc.close(); - } - } -});- \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/PartKeepr.js b/src/PartKeepr/FrontendBundle/Resources/public/js/PartKeepr.js @@ -41,6 +41,12 @@ Ext.application({ Ext.fly(document.body).on('contextmenu', this.onContextMenu, this); }, + getParameter: function (parameter) + { + if (window.parameters[parameter]) { + return window.parameters[parameter]; + } + }, getLoginManager: function () { return this.loginManager; @@ -144,7 +150,9 @@ Ext.application({ displayTipOfTheDayWindow: function () { if (!Ext.data.StoreManager.lookup('TipOfTheDayStore') || !Ext.data.StoreManager.lookup( - 'TipOfTheDayStore').isLoaded()) { + 'TipOfTheDayStore').isLoaded() || !Ext.data.StoreManager.lookup( + 'TipOfTheDayHistoryStore') || !Ext.data.StoreManager.lookup('TipOfTheDayHistoryStore').isLoaded() + ) { this.displayTipWindowTask.delay(100); return; } @@ -152,7 +160,7 @@ Ext.application({ if (PartKeepr.getApplication().getUserPreference("partkeepr.tipoftheday.showtips") !== "false") { var j = Ext.create("PartKeepr.TipOfTheDayWindow"); - if (j.getLastUnreadTip() !== null) { + if (j.hasTips()) { j.show(); } } diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Util/Filter.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Util/Filter.js @@ -0,0 +1,31 @@ +Ext.define('PartKeepr.util.Filter', { + extend: 'Ext.util.Filter', + + /** + * Creates new Filter. + * @param {Object} config Config object + */ + constructor: function (config) + { + + //config.filterFns + + this.operatorFns["notin"] = function (candidate) + { + var v = this._filterValue; + return !Ext.Array.contains(v, this.getCandidateValue(candidate, v)); + }; + //<debug> + var warn = Ext.util.Filter.isInvalid(config); + if (warn) { + Ext.log.warn(warn); + } + //</debug> + this.initConfig(config); + }, + + preventConvert: { + 'in': 1, + 'notin': 1 + }, +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -51,6 +51,7 @@ {% endfor %} {% javascripts output='js/compiled/main2.js' + '@PartKeeprFrontendBundle/Resources/public/js/Util/Filter.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Auth/LoginManager.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.grid.feature.Summary-selectorFix.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Auth/AuthenticationProvider.js' @@ -76,7 +77,6 @@ '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/NumericField.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Widgets/CurrencyNumberField.js' '@PartKeeprFrontendBundle/Resources/public/js/form/field/SearchField.js' - '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/Iframe.js' '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/ClearableComboBox.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/ServiceCall.js' '@PartKeeprFrontendBundle/Resources/public/js/org.jerrymouse.util.locale/locale.js' diff --git a/src/PartKeepr/TipOfTheDayBundle/Action/GetTipHistoryCollectionAction.php b/src/PartKeepr/TipOfTheDayBundle/Action/GetTipHistoryCollectionAction.php @@ -0,0 +1,60 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\Action; + +use Dunglas\ApiBundle\Action\ActionUtilTrait; +use Dunglas\ApiBundle\Exception\RuntimeException; +use Dunglas\ApiBundle\Model\DataProviderInterface; +use PartKeepr\AuthBundle\Services\UserService; +use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory; +use Symfony\Component\HttpFoundation\Request; + +class GetTipHistoryCollectionAction +{ + use ActionUtilTrait; + + /** + * @var DataProviderInterface + */ + private $dataProvider; + + /** + * @var UserService + */ + private $userService; + + public function __construct(DataProviderInterface $dataProvider, UserService $userService) + { + $this->dataProvider = $dataProvider; + $this->userService = $userService; + } + + /** + * Retrieves a filtered tip of the day history list. Filters by the currently logged in user by default + * + * @param Request $request + * + * @return array|\Dunglas\ApiBundle\Model\PaginatorInterface|\Traversable + * + * @throws RuntimeException + */ + public function __invoke(Request $request) + { + list($resourceType) = $this->extractAttributes($request); + + $collection = $this->dataProvider->getCollection($resourceType); + $user = $this->userService->getUser(); + + $resultCollection = array(); + + foreach ($collection as $item) { + /** + * @var $item TipOfTheDayHistory + */ + if ($item->getUser() == $user) { + $resultCollection[] = $item; + } + } + + return $resultCollection; + } +} diff --git a/src/PartKeepr/TipOfTheDayBundle/Action/MarkTipReadAction.php b/src/PartKeepr/TipOfTheDayBundle/Action/MarkTipReadAction.php @@ -0,0 +1,70 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\Action; + +use Doctrine\ORM\EntityManager; +use Dunglas\ApiBundle\Action\ActionUtilTrait; +use Dunglas\ApiBundle\Exception\RuntimeException; +use Dunglas\ApiBundle\Model\DataProviderInterface; +use PartKeepr\AuthBundle\Services\UserService; +use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay; +use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory; +use Symfony\Component\HttpFoundation\Request; + +class MarkTipReadAction +{ + use ActionUtilTrait; + + /** + * @var DataProviderInterface + */ + private $dataProvider; + + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var UserService + */ + private $userService; + + public function __construct( + DataProviderInterface $dataProvider, + EntityManager $entityManager, + UserService $userService + ) { + $this->dataProvider = $dataProvider; + $this->entityManager = $entityManager; + $this->userService = $userService; + } + + /** + * Marks a specific tip as read + * + * @param Request $request The request + * @param int $id The ID of the system notice + * + * @return array|\Dunglas\ApiBundle\Model\PaginatorInterface|\Traversable + * + * @throws RuntimeException + */ + public function __invoke(Request $request, $id) + { + list($resourceType) = $this->extractAttributes($request); + + $tip = $this->getItem($this->dataProvider, $resourceType, $id); + + /** + * @var $tip TipOfTheDay + */ + $tipOfTheDayHistoryItem = new TipOfTheDayHistory(); + $tipOfTheDayHistoryItem->setUser($this->userService->getUser()); + $tipOfTheDayHistoryItem->setName($tip->getName()); + + $this->entityManager->persist($tipOfTheDayHistoryItem); + $this->entityManager->flush(); + + return $tip; + } +} diff --git a/src/PartKeepr/TipOfTheDayBundle/Action/MarkTipsAsUnreadAction.php b/src/PartKeepr/TipOfTheDayBundle/Action/MarkTipsAsUnreadAction.php @@ -0,0 +1,49 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\Action; + +use Doctrine\ORM\EntityManager; +use Dunglas\ApiBundle\Action\ActionUtilTrait; +use Dunglas\ApiBundle\Exception\RuntimeException; +use PartKeepr\AuthBundle\Services\UserService; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MarkTipsAsUnreadAction +{ + use ActionUtilTrait; + + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var UserService + */ + private $userService; + + public function __construct(EntityManager $entityManager, UserService $userService) + { + $this->entityManager = $entityManager; + $this->userService = $userService; + } + + /** + * Marks all tips as unread + * + * @param Request $request The request + * + * @return array|\Dunglas\ApiBundle\Model\PaginatorInterface|\Traversable + * + * @throws RuntimeException + */ + public function __invoke(Request $request) + { + $dql = "DELETE FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory th WHERE th.user = :user"; + $query = $this->entityManager->createQuery($dql); + $query->setParameter("user", $this->userService->getUser()); + $query->execute(); + + return new Response("OK"); + } +} diff --git a/src/PartKeepr/TipOfTheDayBundle/Console/Command/SyncTipsCommand.php b/src/PartKeepr/TipOfTheDayBundle/Console/Command/SyncTipsCommand.php @@ -0,0 +1,24 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\Console\Command; + +use PartKeepr\AuthBundle\Entity\User; +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + + +class SyncTipsCommand extends ContainerAwareCommand +{ + protected function configure() + { + $this + ->setName('partkeepr:cron:synctips') + ->setDescription('Syncronizes the tips from the PartKeepr website'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->getContainer()->get("partkeepr.tip_of_the_day_service")->syncTips(); + } + +} diff --git a/src/PartKeepr/TipOfTheDayBundle/DataFixtures/TipOfTheDayLoader.php b/src/PartKeepr/TipOfTheDayBundle/DataFixtures/TipOfTheDayLoader.php @@ -0,0 +1,22 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\DataFixtures; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\Persistence\ObjectManager; +use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay; + +class TipOfTheDayLoader extends AbstractFixture +{ + public function load(ObjectManager $manager) + { + $tipOfTheDay = new TipOfTheDay(); + $tipOfTheDay->setName("FOO"); + + $manager->persist($tipOfTheDay); + $manager->flush(); + + $this->addReference("tipoftheday", $tipOfTheDay); + } +} + + diff --git a/src/PartKeepr/TipOfTheDayBundle/DependencyInjection/PartKeeprTipOfTheDayExtension.php b/src/PartKeepr/TipOfTheDayBundle/DependencyInjection/PartKeeprTipOfTheDayExtension.php @@ -0,0 +1,26 @@ +<?php + +namespace PartKeepr\TipOfTheDayBundle\DependencyInjection; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * This is the class that loads and manages your bundle configuration + * + * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html} + */ +class PartKeeprTipOfTheDayExtension extends Extension +{ + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('actions.xml'); + $loader->load('services.xml'); + } +} diff --git a/src/PartKeepr/TipOfTheDayBundle/Entity/TipOfTheDay.php b/src/PartKeepr/TipOfTheDayBundle/Entity/TipOfTheDay.php @@ -3,9 +3,7 @@ namespace PartKeepr\TipOfTheDayBundle\Entity; use Doctrine\ORM\Mapping as ORM; use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; -use PartKeepr\PartKeepr; use PartKeepr\Util\BaseEntity; -use PartKeepr\Util\Configuration; use Symfony\Component\Serializer\Annotation\Groups; /** @@ -48,73 +46,4 @@ class TipOfTheDay extends BaseEntity { return $this->name; } - - /** - * Syncronizes the tip database against the master wiki. - * - * @throws \Exception - */ - public static function syncTips() - { - if (ini_get("allow_url_fopen") == 0) { - throw new \Exception("allow_url_fopen is disabled, but required to query the TipOfTheDay database."); - } - - $url = Configuration::getOption("partkeepr.tipoftheday.api", - "http://partkeepr.org/wiki/api.php?action=query&list=categorymembers&cmtitle=Category:TipOfTheDay&format=json"); - - $tipsString = file_get_contents($url); - - - $aPageNames = self::extractPageNames($tipsString); - - self::updateTipDatabase($aPageNames); - } - - /** - * Updates the tip database. Expects an array of page names. - * - * This method clears all page names and re-creates them. This saves - * alot of engineering, because we don't need to match contents - * within the database against contents in an array. - * - * @param array $aPageNames The page names as array. Page names are stored as string. - */ - private static function updateTipDatabase(array $aPageNames) - { - $dql = "DELETE FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay"; - $query = PartKeepr::getEM()->createQuery($dql); - - $query->execute(); - - foreach ($aPageNames as $pageName) { - $tip = new TipOfTheDay(); - $tip->setName($pageName); - PartKeepr::getEM()->persist($tip); - } - - PartKeepr::getEM()->flush(); - } - - /** - * Extracts the page names from the mediawiki JSON returned. - * - * @param string $response The encoded json string - * - * @return array An array with the titles of each page - */ - private static function extractPageNames($response) - { - $aTipsStructure = json_decode($response, true); - $aTips = $aTipsStructure["query"]["categorymembers"]; - - $aPageNames = array(); - - foreach ($aTips as $tip) { - $aPageNames[] = $tip["title"]; - } - - return $aPageNames; - } - } diff --git a/src/PartKeepr/TipOfTheDayBundle/PartKeeprTipOfTheDayBundle.php b/src/PartKeepr/TipOfTheDayBundle/PartKeeprTipOfTheDayBundle.php @@ -1,8 +1,17 @@ <?php namespace PartKeepr\TipOfTheDayBundle; +use PartKeepr\TipOfTheDayBundle\Console\Command\SyncTipsCommand; +use Symfony\Component\Console\Application; use Symfony\Component\HttpKernel\Bundle\Bundle; class PartKeeprTipOfTheDayBundle extends Bundle { + /** + * {@inheritDoc} + */ + public function registerCommands(Application $application) + { + $application->add(new SyncTipsCommand()); + } } diff --git a/src/PartKeepr/TipOfTheDayBundle/Resources/config/actions.xml b/src/PartKeepr/TipOfTheDayBundle/Resources/config/actions.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" ?> + +<container xmlns="http://symfony.com/schema/dic/services" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + <services> + <service id="partkeepr.tip_of_the_day.mark_read" class="PartKeepr\TipOfTheDayBundle\Action\MarkTipReadAction"> + <argument type="service" id="api.data_provider"/> + <argument type="service" id="doctrine.orm.entity_manager"/> + <argument type="service" id="partkeepr.userservice"/> + </service> + <service id="partkeepr.tip_of_the_day.mark_all_unread" + class="PartKeepr\TipOfTheDayBundle\Action\MarkTipsAsUnreadAction"> + <argument type="service" id="doctrine.orm.entity_manager"/> + <argument type="service" id="partkeepr.userservice"/> + </service> + <service id="partkeepr.tip_of_the_day_history.collection_get" + class="PartKeepr\TipOfTheDayBundle\Action\GetTipHistoryCollectionAction"> + <argument type="service" id="api.data_provider"/> + <argument type="service" id="partkeepr.userservice"/> + </service> + </services> +</container> diff --git a/src/PartKeepr/TipOfTheDayBundle/Resources/config/services.xml b/src/PartKeepr/TipOfTheDayBundle/Resources/config/services.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" ?> + +<container xmlns="http://symfony.com/schema/dic/services" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + <services> + <service id="partkeepr.tip_of_the_day_service" class="PartKeepr\TipOfTheDayBundle\Services\TipOfTheDayService"> + <argument type="service" id="service_container"/> + <argument type="service" id="doctrine.orm.entity_manager"/> + </service> + </services> +</container> diff --git a/src/PartKeepr/TipOfTheDayBundle/Services/TipOfTheDayService.php b/src/PartKeepr/TipOfTheDayBundle/Services/TipOfTheDayService.php @@ -0,0 +1,95 @@ +<?php + + +namespace PartKeepr\TipOfTheDayBundle\Services; + + +use Doctrine\ORM\EntityManager; +use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class TipOfTheDayService +{ + + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var EntityManager + */ + private $entityManager; + + public function __construct(ContainerInterface $container, EntityManager $entityManager) + { + $this->container = $container; + $this->entityManager = $entityManager; + } + + /** + * Syncronizes the tip database against the master wiki. + * + * @throws \Exception + */ + public function syncTips() + { + if (ini_get("allow_url_fopen") == 0) { + throw new \Exception("allow_url_fopen is disabled, but required to query the TipOfTheDay database."); + } + + $uri = $this->container->getParameter("partkeepr.tip_of_the_day_list"); + + $tipsString = file_get_contents($uri); + + $aPageNames = $this->extractPageNames($tipsString); + + $this->updateTipDatabase($aPageNames); + } + + /** + * Updates the tip database. Expects an array of page names. + * + * This method clears all page names and re-creates them. This saves + * alot of engineering, because we don't need to match contents + * within the database against contents in an array. + * + * @param array $aPageNames The page names as array. Page names are stored as string. + */ + private function updateTipDatabase(array $aPageNames) + { + $dql = 'DELETE FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay'; + $query = $this->entityManager->createQuery($dql); + + $query->execute(); + + foreach ($aPageNames as $pageName) { + $tip = new TipOfTheDay(); + $tip->setName($pageName); + $this->entityManager->persist($tip); + } + + $this->entityManager->flush(); + } + + /** + * Extracts the page names from the mediawiki JSON returned. + * + * @param string $response The encoded json string + * + * @return array An array with the titles of each page + */ + private function extractPageNames($response) + { + $aTipsStructure = json_decode($response, true); + $aTips = $aTipsStructure["query"]["categorymembers"]; + + $aPageNames = array(); + + foreach ($aTips as $tip) { + $aPageNames[] = $tip["title"]; + } + + return $aPageNames; + } +} diff --git a/src/PartKeepr/TipOfTheDayBundle/Tests/TipOfTheDayTest.php b/src/PartKeepr/TipOfTheDayBundle/Tests/TipOfTheDayTest.php @@ -0,0 +1,91 @@ +<?php +namespace PartKeepr\TipOfTheDayBundle\Tests; + +use Doctrine\Common\DataFixtures\ProxyReferenceRepository; +use Dunglas\ApiBundle\Api\IriConverter; +use Liip\FunctionalTestBundle\Test\WebTestCase; + +class TipOfTheDayTest extends WebTestCase +{ + /** + * @var ProxyReferenceRepository + */ + protected $fixtures; + + public function setUp() + { + $this->fixtures = $this->loadFixtures( + array( + 'PartKeepr\TipOfTheDayBundle\DataFixtures\TipOfTheDayLoader', + 'PartKeepr\AuthBundle\DataFixtures\LoadUserData', + ) + )->getReferenceRepository(); + } + + public function testTips() + { + $client = static::makeClient(true); + + $tip = $this->fixtures->getReference("tipoftheday"); + + /** + * @var $iriConverter IriConverter + */ + $iriConverter = $this->getContainer()->get("api.iri_converter"); + + $iri = $iriConverter->getIriFromItem($tip); + $iri .= "/markTipRead"; + + $client->request( + 'PUT', + $iri + ); + + $response = json_decode($client->getResponse()->getContent()); + + $this->assertObjectHasAttribute("name", $response); + $this->assertObjectHasAttribute("@type", $response); + + $this->assertEquals("TipOfTheDay", $response->{"@type"}); + $this->assertEquals("FOO", $response->name); + + $client->request( + 'GET', + '/api/tip_of_the_day_histories' + ); + + $response = json_decode($client->getResponse()->getContent()); + + $this->assertObjectHasAttribute("@type", $response); + $this->assertObjectHasAttribute("hydra:member", $response); + + $this->assertEquals("hydra:Collection", $response->{"@type"}); + + $this->assertArrayHasKey(0, $response->{"hydra:member"}); + $this->assertEquals("FOO", $response->{"hydra:member"}[0]->name); + + + $client->request( + 'POST', + '/api/tip_of_the_days/markAllTipsAsUnread' + ); + + $this->assertEquals("OK", $client->getResponse()->getContent()); + + $client->request( + 'GET', + '/api/tip_of_the_day_histories' + ); + + $response = json_decode($client->getResponse()->getContent()); + + $this->assertObjectHasAttribute("@type", $response); + $this->assertObjectHasAttribute("hydra:member", $response); + + $this->assertEquals("hydra:Collection", $response->{"@type"}); + + $this->assertEquals(0, count($response->{"hydra:member"})); + + + } +} diff --git a/src/backend/PartKeepr/TipOfTheDay/TipOfTheDayService.php b/src/backend/PartKeepr/TipOfTheDay/TipOfTheDayService.php @@ -1,86 +0,0 @@ -<?php -namespace PartKeepr\TipOfTheDay; - -use PartKeepr\PartKeepr; -use PartKeepr\Service\RestfulService; -use PartKeepr\Service\Service; -use PartKeepr\Session\SessionManager; -use PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory; -use PartKeepr\Util\Configuration; - -class TipOfTheDayService extends Service implements RestfulService { - /** - * Returns all tips along with the information wether they are read or not. - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::get() - */ - public function get () { - $aTips = array(); - $url = Configuration::getOption("partkeepr.tipoftheday.wiki", "http://partkeepr.org/wiki/index.php/"); - - /* Extract all tips which aren't read */ - $dql = "SELECT d FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay d WHERE d.name NOT IN "; - $dql .= "(SELECT dh.name FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory dh WHERE dh.user = :user)"; - - $query = PartKeepr::getEM()->createQuery($dql); - $query->setParameter("user", SessionManager::getCurrentSession()->getUser()); - - foreach ($query->getResult() as $result) { - $aTips[] = array ( - "name" => $result->getName(), - "read" => false, - "url" => $url.$result->getName() . "?useskin=monobookplain"); - } - - /* Extract all tips which are read */ - $dql = "SELECT d FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDay d WHERE d.name IN "; - $dql .= "(SELECT dh.name FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory dh WHERE dh.user = :user)"; - - $query = PartKeepr::getEM()->createQuery($dql); - $query->setParameter("user", SessionManager::getCurrentSession()->getUser()); - - foreach ($query->getResult() as $result) { - $aTips[] = array ( - "name" => $result->getName(), - "read" => true, - "url" => $url.$result->getName() . "?useskin=monobookplain"); - } - - return array("data" => $aTips); - } - - public function create() {} - public function update () {} - public function destroy () {} - - /** - * Marks a specific tip as read. - * - * Uses the parameter "name" to identify the tip. - */ - public function markTipAsRead () { - $this->requireParameter("name"); - - try { - $th = new TipOfTheDayHistory; - $th->setUser($this->getUser()); - $th->setName($this->getParameter("name")); - - PartKeepr::getEM()->persist($th); - PartKeepr::getEM()->flush(); - } catch (\Exception $e) { - /* Do nothing */ - } - - } - - /** - * Marks all tips as unread for the current user - */ - public function markAllTipsAsUnread () { - $dql = "DELETE FROM PartKeepr\TipOfTheDayBundle\Entity\TipOfTheDayHistory th WHERE th.user = :user"; - $query = PartKeepr::getEM()->createQuery($dql); - $query->setParameter("user", $this->getUser()); - $query->execute(); - } -}- \ No newline at end of file