How create multi site setup in phalcon with some shared controllers , models, views with out duplicating views folder? - phalcon

I need to setup multisite in phalcon where I need some common functionalities to be done among all sites and also will have site specific. Say it would have some common controllers modals and views, if I need anything to be changed in one particular site I should be able to change in that particular site with out affecting other sites. just by creating single view template and extending the controllers and modals. If i need to change anything in all sites then I could be able to change it in a single place.

multisite/shared
├── apps
│ ├── common
│ │ ├── controllers (Register namespace Common/Controller)
│ │ │ ├── IndexController.php
│ │ │ ├── LoginController.php
│ │ │ └── ProductsController.php
│ │ ├── models (Register namespace Common/Model)
│ │ │ └── Products.php
│ │ └── views
│ │ ├── login
│ │ │ └── index.volt
│ │ └── products
│ │ | └── index.volt
| | └──index.volt
│ ├── example.com
│ │ ├── controllers
│ │ │ ├── IndexController.php (extend Common/Controller)
│ │ │ ├── LoginController.php (extend Common/Controller)
│ │ │ ├── ProductsController.php (extend Common/Controller)
│ │ │ └── UsersController.php Site Specific Controller
│ │ ├── models
│ │ │ └── Products.php (extend Common/Model)
| | | └── Users.php (Site Specific Model)
│ │ └── views
│ │ └── products (Other view templates will refer to Common view folder)
│ │ └── index.volt
│ ├── example2.com
│ │ ├── controllers
│ │ │ ├── IndexController.php (extend Common/Controller)
│ │ │ ├── ProductsController.php (extend Common/Controller)
│ │ │ └── SitespecificController.php Site Specific Controller
│ │ ├── models
│ │ │ └── Products.php (extend Common/Model)
| | | └── SiteSpecific.php (Site Specific Model)
│ │ └── views
│ │ └── sitespecific (Other view templates will refer to Common view folder)
│ │ └── index.volt
└── public
└── example.com (Will contain Js CS Images to support site specific theme)
└── example2.com (Will contain Js CS Images to support site specific theme)
└── index.php
Refer : http://monkpal.com/Multisite-Set-up-with-shared-views-controllers-and-modals-Phalcon
Steps to setup multiple site with different domain name
Steps to acheive it
Step 1 : Register the namespaces of common controllers and models
Step 2 : Extend the phalcon view engine to cascading the view (say for example View engine will look for specific template file in site specific view folder if its not exist it will look in common views folder, there is no need to replicate all the template files in all sites views directories, you can overwrite single template file alone).
Step 3 : Extend Phalcon volt to provide Skin path for templates Step 4: Create site specific Volt cache folder
Step 5 : Create seperate folders with sitenames in public folder for js/css/images Step 6: Create common contollers, views, modals
Step 7: Extend common controllers , modals in site specific folderss , Views wil be taken from common folder. if you want to overwrite any template you can overwiite that alone no need all view folder. Step 8 : Set sitename by current domain name. this sitename will be used to register contollers models directries
Step 9: Set two views directory one is common and another is sitename (thi can be done only if you have extened the phalcon view to add two directories refer step 2) Files extended are here this should be in your public directory.
Files extended are here, this should be in your root directory.
custom/CustomVolt.php
<?php
namespace Custom;
use Phalcon\Mvc\View\Engine\Volt;
use Phalcon\Mvc\View\Engine\Volt\Compiler;
class CustomVolt extends Volt
{
public function getCompiler()
{
if (!$this->_compiler) {
$this->_compiler = new VoltCompilerExtension($this->getView());
$this->_compiler->setOptions($this->getOptions());
$this->_compiler->setDI($this->getDI());
}
return $this->_compiler;
}
}
class VoltCompilerExtension extends Volt\Compiler
{
public function compileFile($path, $compiledPath, $extendsMode = null)
{
$skinPath = $this->getOption('skinPath');
if ($skinPath) {
$skinTemplate = str_replace(
$this->getDI()->getView()->getViewsDir(),
$skinPath,
$path);
if (is_readable($skinTemplate)) {
$path = $skinTemplate;
}
}
return parent::compileFile($path, $compiledPath, $extendsMode);
}
}
custom/CustomView.php
use Phalcon\Mvc\View\Exception;
use Phalcon\Mvc\View;
use Phalcon\Cache\BackendInterface;
class CustomView extends View
{
protected $_viewsDirs;
/**
* #var
*/
protected $_eventsManager;
/**
* #param $path
*
* #return $this
*/
public function addViewsDir($path)
{
$this->_viewsDirs = $path;
$this->setViewsDir($path);
return $this;
}
/**
* #param $view
* #param array $vars
*
* #return string
*/
public function getPartial($view, $vars = [])
{
ob_start();
$this->partial($view, $vars);
$content = ob_get_contents();
ob_end_clean();
return $content;
}
protected function _engineRender($engines, $viewPath, $silence, $mustClean, BackendInterface $cache = NULL)
{
if (is_object($cache)) {
throw new Exception('Cache view not supported...');
return;
}
$viewsDirs = is_array($this->_viewsDirs) ? array_reverse($this->_viewsDirs) : [$this->_viewsDir];
$notExists = true;
$viewEnginePath = null;
foreach ($engines as $extension => $engine) {
foreach ($viewsDirs as $viewsDir) {
$viewsDirPath = $this->_basePath . $viewsDir . $viewPath;
$viewEnginePath = $viewsDirPath . $extension;
if (is_file($viewEnginePath)) {
if (is_object($this->_eventsManager)) {
$this->_activeRenderPath = $viewEnginePath;
if($this->_eventsManager->fire('view:beforeRenderView', $this, $viewEnginePath) === false) {
break;
}
}
$engine->render($viewEnginePath, $this->_viewParams, $mustClean);
if (is_object($this->_eventsManager)) {
$this->_eventsManager->fire('view:afterRenderView', $this);
}
$notExists = false;
break 2;
}
}
}
if ($notExists) {
if (is_object($this->_eventsManager)) {
$this->_activeRenderPath = $viewEnginePath;
$this->_eventsManager->fire('view:notFoundView', $this);
}
if (!$silence) {
$exceptionMessage = 'View "'.($viewPath).'" was not found in the views directories';
throw new Exception($exceptionMessage);
return;
}
}
}
}
public/index.php
<?php
use Phalcon\Loader;
use Phalcon\Mvc\Application;
use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\Url as UrlProvider;
use Custom\CustomVolt;
use Custom\CustomView;
if($_SERVER['HTTP_HOST'] == "example.com") {
define('SITENAME',"example.com" );
}
if($_SERVER['HTTP_HOST'] == "example2.com") {
define('SITENAME',"example2.com" );
}
define('APP_PATH', realpath('..') . '/');
try {
$loader = new Loader();
$loader->registerNamespaces(array(
'Common\Controller' => '../app/common/controllers',
'Common\Model' => '../app/common/models',
'Custom' => 'custom'
))->register();
$loader->registerDirs(array(
'../app/'.SITENAME.'/controllers/',
'../app/'.SITENAME.'/models/'
))->register();
$di = new FactoryDefault();
$di->set(
'voltService',
function ($view, $di) {
$volt = new CustomVolt($view, $di);
$volt->setOptions(
array(
"compiledPath" => "../cache/volt/".SITENAME."/",
"compiledExtension" => ".compiled",
'compileAlways' => true,
'skinPath' => '../app/'.SITENAME.'/views/'
)
);
return $volt;
}
);
$di->set(
'view',
function () {
$view = new CustomView();
$view->addViewsDir(array('../app/common/views/','../app/'.SITENAME.'/views/'));
$view->registerEngines(
array(
".volt" => 'voltService'
)
);
return $view;
}
);
$application = new Application($di);
$response = $application->handle();
$response->send();
}
catch (\Exception $e) {
echo "Exception: ", $e->getMessage();
}
To render Js and css site specific in volt tempaltes
You use can like this
{{ stylesheet_link(constant('SITENAME') ~'/css/main.css') }}
{{ javascript_include(constant('SITENAME') ~'/js/main.js') }}

Related

jvm-test-suite common test sources for use in multiple test suites

Using the jvm-test-suite gradle plugin, I would like to be able to create a common test source set for use in other test suites. I envision the structure to look like the following where the sources and resources from common can be used in unit, integration, functional, and performance:
project/
├─ src/
│ ├─ main/
│ ├─ test/
│ │ ├─ common/
│ │ │ ├─ kotlin/
│ │ │ ├─ resources/
│ │ ├─ unit/
│ │ │ ├─ kotlin/
│ │ │ ├─ resources/
│ │ ├─ integration/
│ │ │ ├─ kotlin/
│ │ │ ├─ resources/
│ │ ├─ functional/
│ │ │ ├─ kotlin/
│ │ │ ├─ resources/
│ │ ├─ performance/
│ │ │ ├─ kotlin/
│ │ │ ├─ resources/
So far I have tried the following, which I thought would provide the proper classpaths for each test suite:
#file:Suppress("UnstableApiUsage")
plugins {
`jvm-test-suite`
}
// Register `commonTest` source set
sourceSets {
register("commonTest") {
java {
compileClasspath += named("main").get().output
runtimeClasspath += named("main").get().output
srcDir("src/test/common/kotlin")
}
resources {
srcDir("src/test/common/resources")
}
}
}
// Make `commonTestImplementation` extend from `testImplementation` so that we can use all dependencies that `testImplementation` uses
val commonTestImplementation by configurations.getting {
extendsFrom(configurations.named("testImplementation").get())
}
configure<TestingExtension> {
suites {
val sourceSetMain = sourceSets.named("main").get()
val sourceSetCommon = sourceSets.named("commonTest").get()
// These might be able to just be variables instead of lazy evaluation
val sourceSetMainClasspath = { sourceSetMain.compileClasspath + sourceSetMain.output }
val sourceSetCommonClasspath = { sourceSetMain.compileClasspath + sourceSetMain.output }
val test by getting(JvmTestSuite::class) {
testType.set(TestSuiteType.UNIT_TEST)
sources {
// Add common test compile classpath and outputs to the `unitTest` suite?
compileClasspath += sourceSetCommonClasspath()
runtimeClasspath += output + compileClasspath
java {
setSrcDirs(listOf("src/test/unit/kotlin"))
// I've also tried the following which only works when applied to only 1 test suite but not all. Same with the commented out resources portion directly below
// setSrcDirs(listOf("src/test/unit/kotlin", sourceSetCommon.java.srcDirs))
}
resources {
setSrcDirs(listOf("src/test/unit/resources"))
// setSrcDirs(listOf("src/test/unit/resources", sourceSetCommon.resources.srcDirs))
}
}
}
val functionalTest by registering(JvmTestSuite::class) {
testType.set(TestSuiteType.FUNCTIONAL_TEST)
dependencies {
implementation(project())
}
sources {
// Add common test compile classpath and outputs to the `unitTest` suite?
compileClasspath += sourceSetCommonClasspath()
runtimeClasspath += output + compileClasspath
java {
setSrcDirs(listOf("src/test/functional/kotlin"))
}
resources {
setSrcDirs(listOf("src/test/functional/resources"))
}
}
targets {
all {
testTask.configure {
shouldRunAfter(test)
}
}
}
}
}
}
val functionalTestImplementation by configurations.getting {
extendsFrom(configurations.named("testImplementation").get())
}
From this, I expect to be able to access common test sources in both the unit test (unit) directory and functional test (functional) directory. However, this does not work as expected. Any thoughts/input are greatly appreciated!

How can I write to a file in wwwroot with Asp.Net core 2.0 Webapi

I need a very simple API to allow for the Posting of certain keys.
This keys should be written on a file, but I am having trouble after deploying the app, as I can read the file on a GET Request but the posting does not work.
The message it gives me is
"detail": "Access to the path '....\Keys\Keys.json' is denied.",
Code I am using to write to file:
var path = "wwwroot/Keys/Keys.json";
var result = new List <FireBaseKeysModel> ( );
if (System.IO.File.Exists (path)) {
var initialJson = System.IO.File.ReadAllText (path);
var convertedJson =
JsonConvert.DeserializeObject <List <FireBaseKeysModel>> (initialJson);
try {
result.AddRange (convertedJson);
}
catch {
//
}
}
result.Add(new FireBaseKeysModel() {
AccountId = accountId,
AditionalInfo = addicionalInfo,
DeviceInfo = deviceInfo,
RegistrationKey = registrationKey,
ClientId = clientId
});
var json = JsonConvert.SerializeObject (result.ToArray ( ));
System.IO.File.WriteAllText (path, json);
Anyway I can fix this without needint to change permissions on the server itself?
I have similar task that I need to take logged-in users' upload files and store them on the server. I chose to store them under the folder structure wwwroot/uploads/{ environment }/{ username }/{ YYYY }/{ MM }/{ DD }/.
I am not giving you the exact answer to your problem but these are the steps you might want to try.
Enable static file usage
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// With the usage of static file extensions, you shouldn't need to
// set permissions to folders, if you decide to go with wwwroot.
app.UseStaticFiles();
...
}
Storage service
public interface IStorageService
{
Task<string> UploadAsync(string path, IFormFile content, string
nameWithoutExtension = null);
}
public class LocalFileStorageService : IStorageService
{
private readonly IHostingEnvironment _env;
public LocalFileStorageService(IHostingEnvironment env)
{
_env = env;
}
public async Task<string> UploadAsync(string path, IFormFile content,
string nameWithoutExtension = null)
{
if (content != null && content.Length > 0)
{
string extension = Path.GetExtension(content.FileName);
// Never trust user's provided file name
string fileName = $"{ nameWithoutExtension ?? Guid.NewGuid().ToString() }{ extension }";
// Combine the path with web root and my folder of choice,
// "uploads"
path = Path.Combine(_env.WebRootPath, "uploads", path).ToLower();
// If the path doesn't exist, create it.
// In your case, you might not need it if you're going
// to make sure your `keys.json` file is always there.
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
// Combine the path with the file name
string fullFileLocation = Path.Combine(path, fileName).ToLower();
// If your case, you might just need to open your
// `keys.json` and append text on it.
// Note that there is FileMode.Append too you might want to
// take a look.
using (var fileStream = new FileStream(fullFileLocation, FileMode.Create))
{
await Content.CopyToAsync(fileStream);
}
// I only want to get its relative path
return fullFileLocation.Replace(_env.WebRootPath,
String.Empty, StringComparison.OrdinalIgnoreCase);
}
return String.Empty;
}
}
There should not be a way to fix it without modifying permissions on that folder. (Since you are using System.IO I'm assuming this is Windows and IIS). The worker process usually uses the account that is running the application pool.
By default this account should only have read access to that folder. Without giving him, at least write permission, there should be no way to work around it.
Small off-topic comment: I would not hardcode the wwwroot folder, since the name of that folder is object to configuration and could very well change, I'd use the built in IHostingEnvironment and dependency injection to get the path:
private IHostingEnvironment _env;
public FooController(IHostingEnvironment env) {
_env = env;
}
var webrootFolder = _env.WebRootPath

Asset management in yii2

I have the code like this:
class BootBoxAsset extends AssetBundle
{
public $sourcePath = '#vendor/almasaeed2010/adminlte';
public $css = [
];
public $js = [
'https://cdn.bootcss.com/bootbox.js/4.4.0/bootbox.js'
];
public $depends = [
'plugins\jQuery\jquery-2.2.3.min.js',
'bootstrap\js\bootstrap.min.js',
];
public $jsOptions = ['position' => \yii\web\View::POS_HEAD];
}
As the code shows above, I want to use bootbox.js which requires the related jquery and bootstrap dependency that located in my $sourcePath variable combined with the directories in $depends array,
when I register this asset in my view, there's an error tells me that the Class plugins\jQuery\jquery-2.2.3.min.js does not exist, so what should I do to register the dependency not start from yii directory?
thanks!
this mean that you have wrong path for
your dependency
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
then check for proper path
but for these assets you could use
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];

Change component view location in Asp.Net 5

On ASP.NET 5 a Component view must be in one of two places:
Views/NameOfControllerUsingComponent/Components/ComponentName/Default.cshtml
Views/Shared/Components/ComponentName/Default.cshtml
Is there a way to change this to:
Views/NameOfControllerUsingComponent/Components/ComponentName.cshtml
Views/Shared/Components/ComponentName.cshtml
So basically, remove the folder ComponentName and change the view name from Default.cshtml to ComponentName.cshtml.
For me it makes more sense ... Is it possible?
That convention is only applied if you create a view component that derives from the base ViewComponent provided by the framework.
That class defines the View helpers, which return a ViewViewComponentResult:
public ViewViewComponentResult View<TModel>(string viewName, TModel model)
{
var viewData = new ViewDataDictionary<TModel>(ViewData, model);
return new ViewViewComponentResult
{
ViewEngine = ViewEngine,
ViewName = viewName,
ViewData = viewData
};
}
The ViewViewComponentResult is where the conventions are defined:
private const string ViewPathFormat = "Components/{0}/{1}";
private const string DefaultViewName = "Default";
public async Task ExecuteAsync(ViewComponentContext context)
{
...
string qualifiedViewName;
if (!isNullOrEmptyViewName &&
(ViewName[0] == '~' || ViewName[0] == '/'))
{
// View name that was passed in is already a rooted path, the view engine will handle this.
qualifiedViewName = ViewName;
}
else
{
// This will produce a string like:
//
// Components/Cart/Default
//
// The view engine will combine this with other path info to search paths like:
//
// Views/Shared/Components/Cart/Default.cshtml
// Views/Home/Components/Cart/Default.cshtml
// Areas/Blog/Views/Shared/Components/Cart/Default.cshtml
//
// This supports a controller or area providing an override for component views.
var viewName = isNullOrEmptyViewName ? DefaultViewName : ViewName;
qualifiedViewName = string.Format(
CultureInfo.InvariantCulture,
ViewPathFormat,
context.ViewComponentDescriptor.ShortName,
viewName);
}
...
}
Notice that if you return from your view component the full path to a view as the view name, then the view component will use the specified view.
Something like:
return View("~/Views/Shared/Components/ComponentName.cshtml")
Since there is no way to modify the conventions in ViewViewComponentResult and your approach would only work for view components with a single view, you could build something using the root view paths approach:
Create your own ViewComponent class extending the existing one.
Add new helper methods or hide the existing View methods to return a view using a full path:
public ViewViewComponentResult MyView<TModel>(TModel model)
{
var viewName = string.Format(
"~/Views/Shared/Components/{0}.cshtml",
this.ViewComponentContext.ViewComponentDescriptor.ShortName)
return View(viewName, model);
}
If you add new methods you might be able to add them as extension methods of ViewComponent instead of having to create your own class.
Another alternative would be creating a class SingleViewViewComponent copying the code for ViewComponent but replacing the implementation of ViewViewComponentResult View<TModel>(string viewName, TModel model). Then when creating your view components, you would inherit from SingleViewViewComponent instead of ViewComponent.
Took me a weekend to finally find a way around this that didn't involve writing a custom ViewComponentResult.
in MVC .Net Core, you can add your own IViewLocationExpander to the RazorViewEngineOptions in your startup.cs's ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new CustomLocationExpander());
});
}
This allows you to add custom Paths that are used in conjuction with the ViewLocationExpanderContext ViewName ({0}), ControllerName ({1}).
The main issue is that you can't alter the context's values, which makes it seemingly impossible to change the default View Component's ViewName of Component/ComponentName/Default
Seemingly impossible
Here's the trick, the ExpandViewLocations is called with each View(), each time it doesn't have a fully qualified view path. Which means you can add custom logic. What I did was add a catch to detect ViewComponents in the PopulateValues method, then added to the context.Values dictionary, and then if that dictionary has those custom values, it will prepend to the Paths the list of paths that use my generated view name instead of the context.
It's fully reverse compatible, and shouldn't impact performance one bit.
public class CustomLocationExpander : IViewLocationExpander
{
private const string _CustomViewPath = "CustomViewPath";
private const string _CustomController = "CustomController";
public void PopulateValues(ViewLocationExpanderContext context)
{
Regex DefaultComponentDetector = new Regex(#"^((?:[Cc]omponents))+\/+([\w\.]+)\/+(.*)");
/*
* If successful,
* Group 0 = FullMatch (ex "Components/MyComponent/Default")
* Group 1 = Components (ex "Component")
* Group 2 = Component Name (ex "MyComponent")
* Group 3 = View Name (ex "Default")
* */
var DefaultComponentMatch = DefaultComponentDetector.Match(context.ViewName);
if (DefaultComponentMatch.Success)
{
// Will render Components/ComponentName as the new view name
context.Values.Add(_CustomViewPath, string.Format("{0}/{1}", DefaultComponentMatch.Groups[1].Value, DefaultComponentMatch.Groups[2].Value));
context.Values.Add(_CustomController, context.ControllerName);
}
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
/* Parameters:
* {2} - Area Name
* {1} - Controller Name
* {0} - View Name
* */
List<string> Paths = new List<string> {
// Default View Locations to support imported / legacy paths
"/Views/{1}/{0}.cshtml",
"/Views/Shared/{0}.cshtml",
// Adds Feature Folder Rendering
"/Features/{1}/{0}.cshtml",
"/Features/Shared/{0}.cshtml",
// Handles My Custom rendered views
"/{0}.cshtml"
};
// Add "Hard Coded" custom view paths to checks, along with the normal default view paths for backward compatibility
if (context.Values.ContainsKey(_CustomViewPath))
{
// Generate full View Paths with my custom View Name and Controller Name
var CombinedPaths = new List<string>(Paths.Select(x => string.Format(x, context.Values[_CustomViewPath], context.Values[_CustomController], "")));
// Add in original paths for backward compatibility
CombinedPaths.AddRange(Paths);
return CombinedPaths;
}
// Returns the normal view paths
return Paths;
}
}

JTree null pointer exception

If i put path to my CD drive it list all files and folders as it should, but if I put a path to a hard driver partition i get NullPointerException. Can some one help me with this. I can not get to the bottom of this. What is different between CD folders structures and partition structure O.o System.out.print is working fine for both CD and HDD partition. Here is a code:
import java.awt.Dimension;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
/**
*
* #author Anak1n
*/
public class gui extends JPanel {
private final JTree tree;
TreeModel model;
JFileChooser chooser = new JFileChooser();
JButton load;
File fileRoot = new File("F:/");
DefaultMutableTreeNode root;
public gui() {
root = new DefaultMutableTreeNode();
getList(root, fileRoot);
tree = new JTree(root);
tree.setPreferredSize(new Dimension(300, 400));
tree.setRootVisible(false);
add(new JScrollPane((JTree) tree), "Center");
}
public void getList(DefaultMutableTreeNode node, File f) {
if (f.isDirectory()) {
System.out.println(f.getName());
DefaultMutableTreeNode child = new DefaultMutableTreeNode(f);
node.add(child);
File fList[] = f.listFiles();
for (File fList1 : fList) {
getList(child, fList1);
}
}
if (f.isFile()) {
DefaultMutableTreeNode child = new DefaultMutableTreeNode(f);
node.add(child);
}
}
}
Yes problem is in windows folder permission. This solves some of the problems:
if ( f.isDirectory() && !f.getName().equals("$RECYCLE.BIN") && !f.getName().equals("System Volume Information") &&
!f.getName().equals("$Recycle.Bin") && !f.getName().equals("Config.Msi"))
But there is lot more locked files that can not be accessed.