murph-doc/search/search_index.json
2024-03-31 15:19:55 +00:00

1 line
93 KiB
JSON

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Murph","text":"<p>Murph is an open-source CMF built on top of Symfony that helps you to build your own CMS with several domains and languages. It comes with a fully implemented and customizable tree manager, a CRUD generator, a 2FA authentication, settings and tasks managers, basic web analytics.</p> <p>Symfony developers will love build on Murph \ud83d\udcaa End users will be fond of the interface and the powerful tools \ud83d\udc9c</p> <p>Developed with love by Simon Vieille.</p> <p>Support: Murph project on Matrix.</p> <p>Access the demo.</p>"},{"location":"abtesting/","title":"A/B Testing","text":""},{"location":"abtesting/#overview","title":"Overview","text":"<p>Murph contains a basic tools to create A/B Tests.</p> <p>The logic of the implement follows this logic:</p> <ul> <li>A node can be configured to enable A/B Test. You also define a code to set the name of the test</li> <li>When Murph receives a request, an event is dispatched and contains an <code>App\\Core\\Ab\\AbTestInterface</code> object</li> <li>You must create an event subscriber and set variations defined by<ul> <li>a name (a string)</li> <li>a value (whatever you want)</li> <li>a percentage chance (optional)</li> </ul> </li> <li>Then a variation is picked and saved in user cookies</li> <li>Finally, you can retrieve the picked variation PHP side and template side</li> </ul>"},{"location":"abtesting/#configure-the-node","title":"Configure the node","text":"<p>Go the navigation and edit the tested node:</p> <ul> <li>Enable A/B testing</li> <li>Define a code (eg: <code>example_test</code>)</li> </ul> <p></p>"},{"location":"abtesting/#the-event-subscriber","title":"The Event Subscriber","text":"<p>The event subscriber helps you to define each variation and the TTL.</p> src/EventSubscriber/MyAbTestEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\EventSubscriber\\AbEventSubscriber as EventSubscriber;\nuse App\\Core\\Event\\Ab\\AbTestEvent;\n\nclass MyAbTestEventSubscriber extends EventSubscriber\n{\n public function onInit(AbTestEvent $event)\n {\n if ($event-&gt;getTest()-&gt;getName() !== 'example_test') {\n return;\n }\n\n $event-&gt;getTest()\n -&gt;addVariation('test_1', 'Value #1', 20) // 20% of chance\n -&gt;addVariation('test_2', 'Value #2', 30) // 30% of chance\n -&gt;addVariation('test_3', 'Value #3', 50) // 50% of chance\n -&gt;setDuration(3600 * 24) // duration of the cookie in seconds\n ;\n }\n\n public function onRun(AbTestEvent $event)\n {\n // executed if a variation is newly picked\n }\n}\n</code></pre>"},{"location":"abtesting/#the-result","title":"The result","text":"<p>you can retrieve the test and the variation picked in PHP side and in template side.</p> <pre><code>use App\\Core\\Ab\\AbContainerInterface;\n\npublic function foo(AbContainerInterface $testContainer)\n{\n if ($testContainer-&gt;has('example_test')) {\n $test = $testContainer-&gt;get('example_test');\n\n $result = $test-&gt;getResult(); // eg: \"test_2\"\n $value = $test-&gt;getResultValue(); // eg: \"Value #2\"\n\n // ...\n }\n\n // ...\n}\n</code></pre> <pre><code>{% if ab_test_exists('example_test') %}\n {% set test = ab_test('example_test') %}\n {% set result = ab_test_result('example_test') %}\n {% set value = ab_test_value('example_test') %}\n\n {# ... #}\n{% endif %}\n</code></pre>"},{"location":"abtesting/#global-ab-test","title":"Global A/B Test","text":"<p>If you need to perform an A/B test everywhere, you need to create a specific listener:</p> src/EventListener/CustomAbListener.php<pre><code>namespace App\\EventListener;\n\nuse App\\Core\\EventListener\\AbListener as EventListener;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpKernel\\Event\\RequestEvent;\n\nclass CustomAbListener extends EventListener\n{\n /**\n * {@inheritdoc}\n */\n protected function supports(Request $request): bool\n {\n return true;\n }\n\n /**\n * {@inheritdoc}\n */\n protected function getAbTestCode(): string\n {\n return 'my_global_ab_test_code';\n }\n}\n</code></pre> <p><code>CustomAbListener</code> must be registred:</p> config/services.yml<pre><code>services:\n # ...\n\n App\\EventListener\\CustomAbListener;\n tags:\n - { name: kernel.event_listener, event: kernel.request }\n - { name: kernel.event_listener, event: kernel.response }\n</code></pre>"},{"location":"controller/","title":"Controller","text":""},{"location":"controller/#controller_1","title":"Controller","text":"<p>The default controller of a node is <code>App\\Core\\Controller\\Site\\PageController::show</code>. <code>PageController</code> extends <code>Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController</code> and implements very basic features: a Response builder which retrieves the good template and injects variables to the view.</p> <p>To create a custom controller, do this way:</p> src/Controller/MyController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Controller\\Site\\PageController;\n\nclass MyController extends PageController\n{\n public function myAction()\n {\n if (!$this-&gt;siteRequest-&gt;getPage()) {\n throw $this-&gt;createNotFoundException();\n }\n\n return $this-&gt;defaultRender($this-&gt;siteRequest-&gt;getPage()-&gt;getTemplate(), [\n // view datas\n ]);\n }\n}\n</code></pre> <p>Then edit <code>config/packages/app.yaml</code> and add your controller:</p> <pre><code>core:\n site:\n controllers:\n - {name: 'My action', action: 'App\\Controller\\MyController::myAction'}\n</code></pre>"},{"location":"controller/#urlgenerator","title":"UrlGenerator","text":"<p>If your controller represents entities and if the associated node is visible in the sitemap, you can use a <code>App\\Core\\Annotation\\UrlGenerator</code> in annotations and implement a generator. See the example below.</p> src/UrlGenerator/MyEntityGenerator<pre><code>namespace App\\UrlGenerator;\n\nuse App\\Core\\Entity\\Site\\Node;\nuse App\\Repository\\MyEntityRepositoryQuery;\nuse Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface;\n\nclass MyEntityGenerator\n{\n protected MyEntityRepositoryQuery $query;\n protected UrlGeneratorInterface $urlGenerator;\n\n public function __construct(MyEntityRepositoryQuery $query, UrlGeneratorInterface $urlGenerator)\n {\n $this-&gt;query = $query;\n $this-&gt;urlGenerator = $urlGenerator;\n }\n\n public function myActionGenerator(Node $node, array $options): array\n {\n $entities = $this-&gt;query-&gt;create()-&gt;find();\n\n $urls = [];\n\n foreach ($entities as $entity) {\n $urls[] = $this-&gt;urlGenerator-&gt;generate(\n $node-&gt;getRouteName(),\n [\n 'entity' =&gt; $entity-&gt;getId(),\n '_domain' =&gt; $options['_domain'],\n ],\n UrlGeneratorInterface::ABSOLUTE_URL\n );\n }\n\n return $urls;\n }\n}\n</code></pre> <p>Then, the have to annotate the controller like this (note: <code>options</code> is optional):</p> src/Controller/MyController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Annotation\\UrlGenerator;\nuse App\\Core\\Controller\\Site\\PageController;\nuse App\\UrlGenerator\\MyEntityGenerator;\n\nclass MyController extends PageController\n{\n #[UrlGenerator(service: MyEntityGenerator::class, method: 'myActionGenerator', options=[])]\n public function myAction(MyEntity $entity)\n {\n // do stuff\n }\n}\n</code></pre> <p>Finally, update <code>config/services.yaml</code>:</p> <pre><code>services:\n # ...\n\n App\\UrlGenerator\\MyEntityGenerator:\n public: true\n</code></pre>"},{"location":"procedure/","title":"Installation","text":""},{"location":"procedure/#setting-up-the-skeleton","title":"Setting up the skeleton","text":"<p>Depending of you environment, PHP and composer could be located in specific paths. Defines theme with environment vars:</p> <pre><code>export PHP_BIN=/usr/bin/php\nexport COMPOSER_BIN=/usr/bin/composer\nexport NPM_BIN=/usr/bin/npm\nexport YARN_BIN=/usr/bin/yarn\n</code></pre> <p>Create your project:</p> <pre><code>\"$COMPOSER_BIN\" create-project murph/murph-skeleton my_project ^1\n</code></pre> <p>An error occured because of the unconfigured database.</p> <ul> <li>Copy <code>.env</code> into <code>.env.local</code></li> <li>Edit <code>.env.local</code> (documentation). Don't forget to set <code>APP_SECRET</code>.</li> <li>Run <code>make build</code></li> </ul>"},{"location":"procedure/#create-an-admin-user","title":"Create an admin user","text":"<p>Run <code>\"$PHP_BIN\" bin/console murph:user:create</code> and answer questions.</p>"},{"location":"procedure/#configure-a-web-server","title":"Configure a web server","text":"<p>Read the documentation of Symfony to configure a web server.</p> <p>In case of a local server, you can use the Symfony Local Web Server. Then go to https://127.0.0.1:8000/admin.</p>"},{"location":"requirements/","title":"Technical Requirements","text":"<ul> <li>Install PHP 8.0 or higher and these PHP extensions (which are installed and enabled by default in most PHP 8 installations): Ctype, iconv, JSON, PCRE, Session, SimpleXML, and Tokenizer;</li> <li>Install Composer, which is used to install PHP packages.</li> <li>Install NodeJS 16 or higher, Yarn and Webpack</li> <li>A created database (MySQL, PostgreSQL or SQLite)</li> <li>build-essential to run <code>make</code></li> </ul>"},{"location":"sources/","title":"Sources codes","text":"<p>Source codes are accessible on Gitnet:</p> <ul> <li>Core</li> <li>Source code the skeleton</li> </ul>"},{"location":"tasks/","title":"Tasks","text":"<p>Tasks are scripts executabled from UI. The creation of tasks is based on events.</p> src/EventSubscriber/MyTaskEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Task\\TaskInitEvent;\nuse App\\Core\\Event\\Task\\TaskRunRequestedEvent;\nuse App\\Core\\EventSubscriber\\Task\\TaskEventSubscriber;\n\nclass MyTaskEventSubscriber extends TaskEventSubscriber\n{\n public function onInit(TaskInitEvent $event)\n {\n $event-&gt;addTask('my_task', 'Example', 'My task');\n }\n\n public function onRunRequest(TaskRunRequestedEvent $event)\n {\n if ('my_task' !== $event-&gt;getTask()) {\n return;\n }\n\n $event-&gt;getOutput()-&gt;writeln('My task is started');\n\n // ...\n\n $event-&gt;getOutput()-&gt;writeln('My task is finished');\n }\n}\n</code></pre> <p></p> <p></p>"},{"location":"template/","title":"Templating","text":""},{"location":"template/#variables","title":"Variables","text":"<p>By default, these variables are given to a CMS view:</p> <ul> <li>The current node is <code>_node</code> and its menu is <code>_menu</code></li> <li>The current navigation is <code>_navigation</code></li> <li>The current locale is <code>_locale</code></li> <li>The CMS store is <code>_store</code></li> </ul>"},{"location":"template/#navigations-and-menus","title":"Navigations and menus","text":"<ul> <li>Retrieve all navigations: <code>_store.navigations</code></li> <li> <p>Retrieve a navigation by its code: <code>_store.navigation('the_code')</code></p> </li> <li> <p>Retrieve all navigation menus: <code>_navigation.menus</code></p> </li> <li>Retrieve a menu by its code: <code>_navigation.menu('the_code')</code></li> <li>Retrieve all nodes of a menu: <code>menu.rootNode.children</code></li> <li> <p>Retrieve visible nodes of a menu: <code>menu.rootNode.children({visible: true})</code></p> </li> <li> <p>Test if a node is the current one: <code>_store.isActiveNode(node)</code></p> </li> <li>Test if a node is or contains the current one: <code>_store.isActiveNode(node, true)</code></li> </ul>"},{"location":"template/#page","title":"Page","text":"<p>You can access a page's blocks this way:</p> <pre><code>{% set myBlock = _page.myBlock.value %}\n\n{{ myBlock }}\n</code></pre>"},{"location":"template/#url-and-path","title":"URL and path","text":"<p>Murph has somes twig functions to manage URLs:</p>"},{"location":"template/#generic-functions","title":"Generic functions","text":"<ul> <li>Same as twig url but catches all exceptions: <code>{{ safe_url(routeName, options, relative) }}</code></li> <li>Same as twig path but catches all exceptions: <code>{{ safe_path(routeName, options, relative) }}</code></li> </ul>"},{"location":"template/#node-functions","title":"Node functions","text":"<ul> <li>Generates a URL using a node: <code>{{ node_url(node, options, relative) }}</code></li> <li>Generates a path using a node: <code>{{ node_path(node, options, relative) }}</code></li> <li>Generates a URL using a node and catches all exceptions: <code>{{ safe_node_url(node, options, relative) }}</code></li> <li>Generates a path using a node and catches all exceptions: <code>{{ safe_node_path(node, options, relative) }}</code></li> </ul> <p>A node may have a disabled URL:</p> <pre><code>{% if not node.disableUrl %}\n {% set path = safe_node_path(node) %}\n {% set url = safe_node_url(node) %}\n{% endif %}\n</code></pre> <p>When the navigation has several domains, you can specify the domain:</p> <pre><code>{% set path = safe_node_path(node, {_domain: _domain}) %}\n{% set url = safe_node_url(node, {_domain: _domain}) %}\n</code></pre>"},{"location":"template/#code-functions","title":"Code functions","text":"<ul> <li>Generates a URL using codes: <code>{{ code_url(menuCode, nodeCode, options, relative) }}</code></li> <li>Generates a path using codes: <code>{{ code_path(menuCode, nodeCode, options, relative) }}</code></li> <li>Generates a URL using codes and catches all exceptions: <code>{{ safe_code_url(menuCode, nodeCode, options, relative) }}</code></li> <li>Generates a path using codes and catches all exceptions: <code>{{ safe_code_path(menuCode, nodeCode, options, relative) }}</code></li> </ul>"},{"location":"template/#filters","title":"Filters","text":"<p>When a content could contains tags (eg: '{{url://my_route}}<code>), use</code>murph_url`. See the example below:</p> Code Output <code>{{ content }}</code> <code>A link to the &lt;a href=\"{{url://contact}}\"&gt;contact page&lt;/a&gt;</code> <code>{{ content|murph_url }}</code> <code>A link to the &lt;a href=\"https://example.com/contact\"&gt;contact page&lt;/a&gt;</code>"},{"location":"template/#string-builder","title":"String builder","text":"<p>The string builder builds a string using a format and an object or an array.</p> <p>Examples:</p> <ul> <li><code>{{ 'Entity ID is {id}'|build_string(myEntity) }}</code> will output: <code>Entity ID is 42</code></li> <li><code>{{ 'Hello, {user.displayName}!'|build_string(myEntity) }}</code> will output <code>Hello, John doe!</code></li> </ul> <p>In case of an not accessible property, no exception will be thrown.</p>"},{"location":"template/#file-attributes","title":"File attributes","text":"<p>Attributes are managed from the file manager. They are accessibles with these filters:</p> Code Result <code>{{ '&lt;img ... alt=\"{{fattr://hash/alt}}\"&gt;'|file_attributes }}</code> <code>&lt;img ... alt=\"Attribute 'alt' of the file with the given hash\"&gt;</code> <code>{{ 'path/to/file'|file_attribute('alt') }}</code> Attribute <code>alt</code> of the given file"},{"location":"users/","title":"Users","text":"<p>Murph provided a basic authentication based on the <code>User</code> entity.</p>"},{"location":"changelog/core/","title":"Changelog","text":"[v1.24.0] - 2024-01-27 <p>Added</p> <ul> <li>add CSS class <code>no-wrap</code></li> <li>copy the pager of the CRUD at the bottom of the list</li> </ul> <p>Fixed</p> <ul> <li>fix an issue with the file manager when editing an item opened in a modal</li> <li>fix type casting in slugifier</li> </ul> [v1.23.0] - 2023-11-01 <p>Added</p> <ul> <li>allow to define templates show before and after a murph collection item</li> <li>add global batch actions</li> <li>add constraint <code>Length</code> in forms</li> <li>add sass classes to mange with of elements</li> <li>set searchFields option on jschoice manager (search on labels)</li> </ul> <p>Changed</p> <ul> <li>refactor services using constructor property promotions</li> <li>remove twig in the mail notifier service</li> <li>change pills colors</li> <li>change border colors of inputs when focused</li> <li>change colors on js-choices element</li> </ul> <p>Fixed</p> <ul> <li>fix regression on crud sorting</li> <li>fix test in RepositoryQuery::addForcedFilterHandler</li> <li>remove parameter $option on CrudConfiguration::setForm and fix CrudController make template</li> <li>fix the aspect of the actions's column in the crud</li> </ul> [v1.22.0] - 2023-09-28 <p>Added</p> <ul> <li>add new options in BooleanField: <code>toggle|checkbox_class_when_true</code> and <code>toggle|checkbox_class_when_false</code></li> <li>add <code>count</code> method in repository query</li> <li>add <code>addForcedFilterHandler</code> method in repository query</li> <li>add <code>inline_form_validation</code> option to validate inline forms with custom algo</li> <li>add crud sorting parameters in the session</li> <li>add flush option in the entity manager on create, update, remove, and persist methods</li> </ul> [1.21.1] - 2023-08-17 <p>Added</p> <ul> <li>add form error handle in inline edit action and refill the form using the previous request content</li> <li>add form error handle in ssettings actions and refill the form using the previous request content</li> </ul> <p>Fixed</p> <ul> <li>fix tinymce reload when modal is closed and reopened</li> <li>fix modal hiding when a file is successfuly uploaded in the file manager</li> </ul> [1.21.0] - 2023-08-11 <p>Added</p> <ul> <li>allow to use array syntax in string builder filter</li> <li>add color property in Navigation</li> <li>add badge with navigation color in admin views</li> <li>add <code>default_value</code> option in crud fields</li> <li>add <code>display</code> option in BooleanField</li> <li>add associated nodes in page form</li> </ul> <p>Fixed</p> <ul> <li>fix routes in the global settings controller</li> </ul> [1.20.0] - 2023-07-27 <p>Added</p> <ul> <li>enable double click on cruds</li> <li>add block class name for the choice type in the page maker</li> <li>update file details view on the file manager</li> <li>add form options in the crud filter action</li> <li>add trans filter in inline form modal title</li> <li>add setter to define all fields in a defined context</li> <li>add filename generator setter in FileUploadHandler</li> <li>add variable for the sidebar size</li> <li>add twig block to override defaults actions in crud index template</li> <li>add option to remove iterable values and/or specifics keys in the twig toArray function</li> <li>add boolean field for CRUD</li> <li>add context variable in each controllers to simplify overrides</li> <li>core.site.name and core.site.logo are not longer required</li> <li>add default templates when a crud is generated</li> <li>add boolean 'is_disabled' in the menu item template options</li> </ul> <p>Fixed</p> <ul> <li>fix filemanager date ordering</li> <li>fix maker CrudController template: remove bad pasted code</li> <li>fix redirect listener: use boolean instead of integer</li> <li>fix responsive of account edit template</li> <li>fix collection widget: allow_add/allow_delete and prototype</li> </ul> <p>Changed</p> <ul> <li>user admin routes are defined in core, custom controller is not required</li> </ul> [1.19.0] - 2023-04-15 <p>Added</p> <ul> <li>feat(page): forms for metas, opengraph and extra informations can be removed</li> <li>feat(navigation): user interface is improved</li> <li>feat(file): webp is allowed and shown in form widgets and in file manager details</li> <li>feat(file): the file manager now show the size and the modification date of a file</li> <li>feat(crud): add option <code>action</code> in field to add a link to the view page or to the edition page</li> <li>feat(crud): add option <code>inline_form</code> in field to configure to edit the data</li> <li>feat(crud): add <code>setDoubleClick</code> in the crud configuration</li> </ul> [1.18.0] - 2023-01-13 <p>Added</p> <ul> <li>feat(dep): add symfony/runtime</li> <li>feat(dep): add symfony/flex</li> </ul> <p>Fixed</p> <ul> <li>fix(crud): allow POST in delete actions</li> <li>fix(crud): remove default page value in abstract crud controller</li> <li>fix(admin): test site_logo before using it</li> <li>fix(ui): update z-index of choices__list--dropdown</li> </ul> [1.17.1] - 2022-12-03 <p>Fixed</p> <ul> <li>add mising attribute on timestampable (doctrine)</li> </ul> [1.17.0] - 2022-11-19 <p>Fixed</p> <ul> <li>fix tinymce modal z-index in tox</li> </ul> <p>Changed</p> <ul> <li>replace annotation with attributes</li> </ul> [1.16.0] - 2022-09-06 <p>Added</p> <ul> <li>add A/B testing feature</li> <li>add cleanup of html string extracted from grapesjs content</li> </ul> <p>Fixed</p> <ul> <li>fix file block type</li> </ul> <p>Changed</p> <ul> <li>remove dashboard action from the core</li> </ul> [1.15.0] - 2022-05-09 <p>Added</p> <ul> <li>CrudConfiguration::setAction can receive a callable instead of a boolean in 'enabled' param</li> <li>add grapesjs-component-code-editor and grapesjs-parser-postcss</li> <li>hide the backoffice site name when small resolution</li> <li>add entity_to_array twig function</li> <li>add default field to show in crud configuration</li> </ul> <p>Fixed</p> <ul> <li>fix the mail notifier</li> <li>fix sitemap: navigation with several domains</li> <li>fix regression with editorjs: content not loaded</li> </ul> <p>Changed</p> <ul> <li>change default template to show an entity using <code>entity_to_array</code></li> </ul> [1.14.1] - 2022-04-30 <p>Added</p> <ul> <li>add allowed chars in RouteParameterSlugify and CodeSlugify</li> <li>improve sidebar in mobile view</li> </ul> <p>Fixed</p> <ul> <li>fix creation of new element when a menu is edited</li> <li>fix editorjs error when the textarea is empty</li> </ul> [1.14.0] - 2022-04-20 <p>Added</p> <ul> <li>add grapesjs modes</li> <li>add tinymce block type</li> <li>add editor types in page maker</li> <li>add the page template when the page is generated with the maker</li> </ul> <p>Changed</p> <ul> <li>replace flag-icon-css with flag-icons</li> </ul> [1.13.0] - 2022-04-17 <p>Added</p> <ul> <li>add editorjs hyperlink block</li> <li>add button to show and hide metas (admin)</li> <li>add grapesjs editor</li> <li>add editorjs type</li> </ul> <p>Fixed</p> <ul> <li>fix editorjs inline tools (bold and italic)</li> </ul> <p>Changed</p> <ul> <li>update editorjs quote block template</li> </ul> [1.12.0] - 2022-03-26 <p>Added</p> <ul> <li>add page maker command (<code>make:page</code>)</li> <li>add CrudConfiguration::getViewData in complement of CrudConfiguration::getViewDatas</li> <li>add editorjs link block endpoint</li> </ul> <p>Fixed</p> <ul> <li>fix issue with empty user-agent in AnalyticListener</li> </ul> <p>Changed</p> <ul> <li>update editorjs image block view</li> </ul> [1.11.0] - 2022-03-22 <p>Added</p> <ul> <li>add data-modal-create attribute to force modal to be open in a new container</li> <li>add blur when several modals are opened</li> <li>add specific form types for Tinymce and EditorJS</li> </ul> <p>Changed</p> <ul> <li>update file-manager with data-modal-create attribute</li> </ul> [1.10.0] - 2022-03-17 <p>Added</p> <ul> <li>add url and path generators using code (twig)</li> </ul> <p>Changed</p> <ul> <li>update node entity constraints</li> </ul> [1.9.2] - 2022-03-14 <p>Fixed</p> <ul> <li>fix issue with murph version constant and autoloader</li> </ul> [1.9.1] - 2022-03-14 <p>Added</p> <ul> <li>add murph version in autoload file</li> </ul> <p>Changed</p> <ul> <li>remove AdminController constructor</li> </ul> [1.9.0] - 2022-03-13 <p>Added</p> <ul> <li>add murph version in admin ui</li> </ul> <p>Changed</p> <ul> <li>the core is now installed with composer</li> </ul> [1.8.0] - 2022-03-10 <p>Added</p> <ul> <li>add security roles in app configuration</li> <li>add option to restrict node access to specific roles</li> </ul> <p>Changed</p> <ul> <li>rename <code>core/EventSuscriber</code> with <code>core/EventSubscriber</code></li> </ul> [1.7.3] - 2022-03-06 <p>Added</p> <ul> <li>add ability to rename file in the file manager</li> </ul> <p>Fixed</p> <ul> <li>fix user factory</li> <li>fix user creation from ui</li> </ul> [1.7.2] - 2022-03-03 <p>Added</p> <ul> <li>add templates to render sections and items in the admin menu</li> </ul> <p>Fixed</p> <ul> <li>fix the analytic table when a path is a long</li> </ul> [1.7.1] - 2022-03-01 <p>Added</p> <ul> <li>add translations</li> </ul> <p>Fixed</p> <ul> <li>fix missing directories</li> </ul> [1.7.0] - 2022-03-01 <p>Fixed</p> <ul> <li>fix the analytic referers table when a referer has a long domain</li> </ul> <p>Changed</p> <ul> <li>upgrade dependencies</li> <li>move assets to the core directory</li> </ul> [1.6.0] - 2022-02-28 <p>Added</p> <ul> <li>add block in field templates to allow override</li> <li>merge route params in crud admin redirects</li> <li>improve murph:user:create command</li> </ul> <p>Fixed</p> <ul> <li>fix form namespace prefix in the crud controller maker</li> <li>fix date field when the value is empty</li> <li>fix crud batch column width</li> <li>fix sidebar icon width</li> <li>fix cache clear task</li> </ul> <p>Changed</p> <ul> <li>remove password generation from the user factory</li> </ul> [1.5.0] - 2022-02-25 <p>Added</p> <ul> <li>add desktop views and mobile views</li> </ul> <p>Changed</p> <ul> <li>upgrade dependencies</li> <li>replace jaybizzle/crawler-detect with matomo/device-detector</li> </ul> [1.4.1] - 2022-02-23 <p>Added</p> <ul> <li>handle app urls in twig routing filters</li> </ul> <p>Fixed</p> <ul> <li>fix views in analytics modal</li> <li>replace empty path with \"/\" in analytics</li> </ul> <p>Changed</p> <ul> <li>update default templates</li> </ul> [1.4.0] - 2022-02-21 <p>Added</p> <ul> <li>add basic analytics</li> </ul> [1.3.0] - 2022-02-19 <p>Added</p> <ul> <li>add support of regexp with substitution in redirect</li> <li>url tags can be used as redirect location</li> <li>add builders to replace file information tags and url tags</li> </ul> <p>Fixed</p> <ul> <li>fix filemanager sorting</li> <li>fix batch action setter</li> </ul> [1.2.0] - 2022-02-14 <p>Added</p> <ul> <li>add sort in file manager</li> <li>add redirect manager</li> </ul> <p>Changed</p> <ul> <li>replace node-sass with sass</li> </ul> [1.1.0] - 2022-02-29 <p>Added</p> <ul> <li>add directory upload in file manager</li> </ul> <p>Fixed</p> <ul> <li>fix admin node routing</li> </ul> <p>Changed</p> <ul> <li>symfony/swiftmailer-bundle is replaced by symfony/mailer</li> </ul> [1.0.1] - 2022-02-25 <p>Fixed</p> <ul> <li>fix Makefile environment vars (renaming)</li> <li>fix composer minimum stability</li> </ul> [1.0.0] - 2022-01-23"},{"location":"changelog/skeleton/","title":"Changelog","text":"[v1.23.0] - 2023-09-28 <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [v1.22.0] - 2023-09-28 <p>Added</p> <ul> <li>update woodpecker ci base file</li> </ul> <p>Fixed</p> <ul> <li>fix #1: add UniqueEntity constraint in the User entity</li> </ul> <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.21.0] - 2023-08-11 <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.20.0] - 2023-07-27 <p>Fixed</p> <ul> <li>fix collection widget: allow_add/allow_delete and prototype</li> </ul> <p>Added</p> <ul> <li>add user admin controller and simples views in default files</li> <li>add chdir in the console entrypoint</li> </ul> <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.19.0] - 2023-04-15 <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.18.0] - 2023-01-13 <p>Added</p> <ul> <li>feat(dep): update dependencies</li> <li>feat(update): apply new recipe for phpunit</li> <li>feat(update): apply recipes:update doctrine/doctrine-bundle</li> <li>feat(update): apply recipes:update doctrine/doctrine-migrations-bundle</li> <li>feat(update): apply recipes:update liip/imagine-bundle</li> <li>feat(update): apply recipes:update stof/doctrine-extensions-bundle</li> <li>feat(update): apply recipes:update symfony/apache-pack</li> <li>feat(update): apply recipes:update symfony/console</li> <li>feat(update): apply recipes:update symfony/debug-bundle</li> <li>feat(update): apply recipes:update symfony/flex</li> <li>feat(update): apply recipes:update symfony/mailer</li> <li>feat(update): apply recipes:update symfony/framework-bundle</li> <li>feat(update): apply recipes:update symfony/monolog-bundle</li> <li>feat(update): apply recipes:update symfony/routing</li> <li>feat(update): apply recipes:update symfony/security-bundle</li> <li>feat(update): apply recipes:update symfony/translation</li> <li>feat(update): apply recipes:update symfony/twig-bundle</li> <li>feat(update): apply recipes:update symfony/validator</li> <li>feat(update): apply recipes:update symfony/web-profiler-bundle</li> <li>feat(update): apply recipes:update symfony/webpack-encore-bundle</li> <li>feat(update): apply recipes:update scheb/2fa-bundle</li> </ul> <p>Fixed</p> <ul> <li>fix(config): fix typo in 2fa conf</li> <li>fix(config): fix firewall config</li> </ul> [1.17.0] - 2022-11-19 <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> <li>replace annotation with attributes</li> <li>use encore from node_modules in npm scripts</li> </ul> [1.16.0] <p>Added</p> <ul> <li>add a admin dashboard controller</li> <li>add meta description in base.html.twig</li> </ul> <p>Fixed</p> <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.15.0] <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.14.3] <p>Added</p> <ul> <li>add blocks in default template</li> </ul> <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.14.2] [1.14.1] <p>Fixed</p> <ul> <li>fix missing envvar in makefile (npm)</li> </ul> [1.14.0] <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.13.0] <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.12.0] <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.11.0] <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> <li>use murph-npm to install npm requirements</li> </ul> [1.10.0] <p>Added</p> <ul> <li>add translated title in dashboard template</li> </ul> <p>Fixed</p> <ul> <li>remove useless env var from makefile</li> </ul> <p>Changed</p> <ul> <li>upgrade murph/murph-core</li> </ul> [1.9.1] - 2022-03-14 <p>Added</p> <ul> <li>add murph version in autoload file</li> </ul> <p>Changed</p> <ul> <li>remove AdminController constructor</li> </ul> [1.9.0] - 2022-03-13 <p>Added</p> <ul> <li>add murph version in admin ui</li> </ul> <p>Changed</p> <ul> <li>the core is now installed with composer</li> </ul> [1.8.0] - 2022-03-10 <p>Added</p> <ul> <li>add security roles in app configuration</li> <li>add option to restrict node access to specific roles</li> </ul> <p>Changed</p> <ul> <li>rename <code>core/EventSuscriber</code> with <code>core/EventSubscriber</code></li> </ul> [1.7.3] - 2022-03-06 <p>Added</p> <ul> <li>add ability to rename file in the file manager</li> </ul> <p>Fixed</p> <ul> <li>fix user factory</li> <li>fix user creation from ui</li> </ul> [1.7.2] - 2022-03-03 <p>Added</p> <ul> <li>add templates to render sections and items in the admin menu</li> </ul> <p>Fixed</p> <ul> <li>fix the analytic table when a path is a long</li> </ul> [1.7.1] - 2022-03-01 <p>Added</p> <ul> <li>add translations</li> </ul> <p>Fixed</p> <ul> <li>fix missing directories</li> </ul> [1.7.0] - 2022-03-01 <p>Fixed</p> <ul> <li>fix the analytic referers table when a referer has a long domain</li> </ul> <p>Changed</p> <ul> <li>upgrade dependencies</li> <li>move assets to the core directory</li> </ul> [1.6.0] - 2022-02-28 <p>Added</p> <ul> <li>add block in field templates to allow override</li> <li>merge route params in crud admin redirects</li> <li>improve murph:user:create command</li> </ul> <p>Fixed</p> <ul> <li>fix form namespace prefix in the crud controller maker</li> <li>fix date field when the value is empty</li> <li>fix crud batch column width</li> <li>fix sidebar icon width</li> <li>fix cache clear task</li> </ul> <p>Changed</p> <ul> <li>remove password generation from the user factory</li> </ul> [1.5.0] - 2022-02-25 <p>Added</p> <ul> <li>add desktop views and mobile views</li> </ul> <p>Changed</p> <ul> <li>upgrade dependencies</li> <li>replace jaybizzle/crawler-detect with matomo/device-detector</li> </ul> [1.4.1] - 2022-02-23 <p>Added</p> <ul> <li>handle app urls in twig routing filters</li> </ul> <p>Fixed</p> <ul> <li>fix views in analytics modal</li> <li>replace empty path with \"/\" in analytics</li> </ul> <p>Changed</p> <ul> <li>update default templates</li> </ul> [1.4.0] - 2022-02-21 <p>Added</p> <ul> <li>add basic analytics</li> </ul> [1.3.0] - 2022-02-19 <p>Added</p> <ul> <li>add support of regexp with substitution in redirect</li> <li>url tags can be used as redirect location</li> <li>add builders to replace file information tags and url tags</li> </ul> <p>Fixed</p> <ul> <li>fix filemanager sorting</li> <li>fix batch action setter</li> </ul> [1.2.0] - 2022-02-14 <p>Added</p> <ul> <li>add sort in file manager</li> <li>add redirect manager</li> </ul> <p>Changed</p> <ul> <li>replace node-sass with sass</li> </ul> [1.1.0] - 2022-02-29 <p>Added</p> <ul> <li>add directory upload in file manager</li> </ul> <p>Fixed</p> <ul> <li>fix admin node routing</li> </ul> <p>Changed</p> <ul> <li>symfony/swiftmailer-bundle is replaced by symfony/mailer</li> </ul> [1.0.1] - 2022-02-25 <p>Fixed</p> <ul> <li>fix Makefile environment vars (renaming)</li> <li>fix composer minimum stability</li> </ul> [1.0.0] - 2022-01-23"},{"location":"crud/","title":"CRUD","text":"<p>Murph helps you to manage specific entities with a CRUD manager:</p> <ul> <li>Create entities</li> <li>Read (Show) entities</li> <li>Update entities</li> <li>Delete entities</li> </ul> <p>You can configure almost anything:</p> <ul> <li>How to list entities:<ul> <li>Information to show</li> <li>Available actions</li> <li>Filters</li> <li>Sorting</li> <li>...</li> </ul> </li> <li>How to show an entity</li> <li>How to create and update an entity</li> </ul>"},{"location":"crud/configuration/","title":"Configuration","text":"<p>A generated crud controller contains a method named <code>getConfiguration</code>. This methods returns a instance of <code>App\\Core\\Crud\\CrudConfiguration</code>.</p>"},{"location":"crud/configuration/#setpagetitle","title":"setPageTitle","text":"<p><code>setPageTitle(string $page, string $title)</code></p> <p>Set the title of the given page.</p> <p>Example of usage in a CRUD template: <code>&lt;title&gt;{{ configuration.pageTitle('index') }}&lt;/title&gt;</code>.</p>"},{"location":"crud/configuration/#setpageroute","title":"setPageRoute","text":"<p><code>setPageRoute(string $page, string $route)</code></p> <p>Set the route of the given page. By default, pages are: <code>index</code>, <code>edit</code>, <code>new</code>, <code>show</code>. You can create a custom page for a custom controller.</p> <p>Example of usage in a CRUD template: <code>&lt;a href=\"{{ path(configuration.pageRoute('new')) }}\"&gt;...&lt;/a&gt;</code>.</p>"},{"location":"crud/configuration/#setform","title":"setForm","text":"<p><code>setForm(string $context, string $form,</code>array<code>$options = [])</code></p> <p>Set the form used in the given context.</p>"},{"location":"crud/configuration/#setformoptions","title":"setFormOptions","text":"<p><code>setFormOptions(string $context,</code>array<code>$options = [])</code></p> <p>Defines options given to a form.</p>"},{"location":"crud/configuration/#setaction","title":"setAction","text":"<p><code>setAction(string $page, string $action, bool|callable $enabled)</code></p> <p>Set if an action is enabled or not in the given page. Take a look at <code>core/Resources/views/admin/crud/*.html.twig</code> for more information. Depending the context, the callable could receive the entity in parameter. Example:</p> <pre><code>-&gt;setAction('index', 'edit', function(EntityInterface $entity) {\n return $entity-&gt;getUser()-&gt;getId() === $this-&gt;getUser()-&gt;getId();\n})\n</code></pre> <p>Usage in a CRUD template: <code>{% if configuration.action('index', 'new')%}...{% endif %}</code>.</p>"},{"location":"crud/configuration/#setactiontitle","title":"setActionTitle","text":"<p><code>setActionTitle(string $page, string $action, string $title)</code></p> <p>Set the title of an action in the given page.</p> <p>Example of usage in a CRUD template: <code>{{ configuration.actionTitle(context, 'new', 'New')|trans }}</code></p>"},{"location":"crud/configuration/#setview","title":"setView","text":"<p><code>setView(string $context, string $view)</code></p> <p>Override a view.</p> Controller (context) View Description <code>index</code> <code>@Core/admin/crud/index.html.twig</code> Template of the page <code>index</code> <code>edit</code> <code>@Core/admin/crud/edit.html.twig</code> Template of the page <code>edit</code> <code>new</code> <code>@Core/admin/crud/new.html.twig</code> Template of the page <code>new</code> <code>show</code> <code>@Core/admin/crud/show.html.twig</code> Template of the page <code>show</code> <code>filter</code> <code>@Core/admin/crud/filter.html.twig</code> Template of the page <code>filter</code> Form (context) View Description <code>form</code> <code>@Core/admin/crud/_form.html.twig</code> Template to render a form <code>form_widget</code> <code>@Core/admin/crud/_form_widget.html.twig</code> Template to render a form widget <code>form_translations</code> <code>@Core/admin/crud/_form_translations.html.twig</code> Template to render a the translation field Entity (context) View Description <code>show_entity</code> <code>@Core/admin/crud/_show.html.twig</code> Template to render the entity"},{"location":"crud/configuration/#setviewdatas","title":"setViewDatas","text":"<p><code>setViewDatas(string $context,</code>array<code>$datas)</code> and <code>addViewData(string $context, string $name, $value)</code></p> <p>Add datas given to a view. Useful in a custom controller.</p>"},{"location":"crud/configuration/#setfield","title":"setField","text":"<p><code>setField(string $context, string $label, string $field,</code>array<code>$options)</code></p> <p>Add a field displayed in the given context. Used in the index.</p> <pre><code>use App\\Core\\Crud\\Field;\n\n$configuration-&gt;setField('index', 'Title', Field\\TextField::class, [\n // options\n])\n</code></pre> <p>All fields have these options:</p> Option Type Default Description <code>property</code> <code>string</code> <code>null</code> Entity's property to display <code>property__builder</code> <code>callable</code> <code>null</code> A callable data and used to generate the content displayed <code>view</code> <code>string</code> <code>@Core/admin/crud/field/text.html.twig</code> The templated rendered <code>default_value</code> <code>string</code> <code>null</code> Default value to display when the property is <code>null</code> <code>action</code> <code>string</code> <code>null</code> An action to perform on click (<code>null</code>, <code>edit</code>, <code>view</code>) <code>raw</code> <code>boolean</code> <code>false</code> Render as HTML <code>sort</code> <code>array | callable</code> <code>null</code> Defines how to sort <code>href</code> <code>string | callable</code> <code>null</code> Data to generate a link <code>href_attr</code> <code>array | callable</code> <code>null</code> Attributes of the link <code>inline_form</code> <code>null | callable</code> <code>null</code> A method to define a form to edit datas <code>inline_form_validation</code> <code>null | callable</code> <code>null</code> A method to define a custom form validation callback Example #0<pre><code>$configuration-&gt;setField('index', 'My field', TextField::class, [\n 'property' =&gt; 'myProperty',\n // OR\n 'property_builder' =&gt; function($entity, array $options) {\n return $entity-&gt;getMyProperty();\n },\n])\n</code></pre> Example #1<pre><code>$configuration-&gt;setField('index', 'My field', TextField::class, [\n 'raw' =&gt; true,\n 'property_builder' =&gt; function($entity, array $options) {\n return sprintf('&lt;span class=\"foo\"&gt;%s&lt;/span&gt;', $entity-&gt;getBar());\n },\n])\n</code></pre> Example #2<pre><code>// https://127.0.0.7:8000/admin/my_entity?_sort=property&amp;_sort_direction=asc\n$configuration-&gt;setField('index', 'My field', TextField::class, [\n 'property' =&gt; 'myProperty'\n 'sort' =&gt; ['property', '.myProperty'],\n // OR\n 'sort' =&gt; ['property', function(MyEntityRepositoryQuery $query, $direction) {\n $query-&gt;orderBy('.myProperty', $direction);\n }],\n])\n</code></pre> Example #3<pre><code>use Symfony\\Component\\Form\\FormBuilderInterface;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;\nuse Symfony\\Component\\Validator\\Constraints\\NotBlank;\n\n$configuration-&gt;setField('index', 'My field', TextField::class, [\n 'property' =&gt; 'myProperty',\n 'inline_form' =&gt; function(FormBuilderInterface $builder, EntityInterface $entity) {\n $builder-&gt;add('myProperty', TextType::class, [\n 'required' =&gt; true,\n 'constraints' =&gt; [new NotBlank()],\n ]);\n }\n])\n</code></pre>"},{"location":"crud/configuration/#textfield","title":"TextField","text":"<p><code>App\\Core\\Crud\\Field\\TextField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/text.html.twig</code> The templated rendered"},{"location":"crud/configuration/#datefield","title":"DateField","text":"<p><code>App\\Core\\Crud\\Field\\DateField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/date.html.twig</code> The templated rendered <code>format</code> <code>string</code> <code>Y-m-d</code> The date format"},{"location":"crud/configuration/#datetimefield","title":"DatetimeField","text":"<p><code>App\\Core\\Crud\\Field\\DatetimeField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/date.html.twig</code> The templated rendered <code>format</code> <code>string</code> <code>Y-m-d H:i:s</code> The date format"},{"location":"crud/configuration/#buttonfield","title":"ButtonField","text":"<p><code>App\\Core\\Crud\\Field\\ButtonField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/button.html.twig</code> The templated rendered <code>button_attr</code> <code>array</code> <code>[]</code> Button HTML attributes <code>button_attr_builder</code> <code>callabled</code> <code>null</code> A callable data and used to generate <code>button__attr</code> <code>button_tag</code> <code>string</code> <code>button</code> HTML tag of the button"},{"location":"crud/configuration/#imagefield","title":"ImageField","text":"<p><code>App\\Core\\Crud\\Field\\ImageField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/image.html.twig</code> The templated rendered <code>image_attr</code> <code>array</code> <code>[]</code> Image HTML attributes"},{"location":"crud/configuration/#booleanfield","title":"BooleanField","text":"<p><code>App\\Core\\Crud\\Field\\BooleanField</code></p> Option Type Default Description <code>view</code> <code>string</code> <code>@Core/admin/crud/field/boolean.html.twig</code> The templated rendered <code>display</code> <code>string</code> <code>toggle</code> Type of render (<code>toggle</code> or <code>checkbox</code>) <code>checkbox_class_when_true</code> <code>string</code> <code>fa-check-square</code> HTML class added when the value is <code>true</code> and display is <code>checkbox</code> <code>checkbox_class_when_false</code> <code>string</code> <code>fa-square</code> HTML class added when the value is <code>false</code> and display is <code>checkbox</code> <code>toggle_class_when_true</code> <code>string</code> <code>bg-success</code> HTML class added when the value is <code>true</code> and display is <code>toggle</code> <code>toggle_class_when_false</code> <code>string</code> <code>bg-secondary</code> HTML class added when the value is <code>false</code> and display is <code>toggle</code>"},{"location":"crud/configuration/#setmaxperpage","title":"setMaxPerPage","text":"<p><code>setMaxPerPage(string $page, int $max)</code></p> <p>Set how many elements are displayed in a single page.</p>"},{"location":"crud/configuration/#seti18n","title":"setI18n","text":"<p><code>setI18n(array $locales, string $defaultLocale)</code></p> <p>Set an array of locales for a translatable entity. The default locale is used in the index page. Compatible with https://github.com/KnpLabs/DoctrineBehaviors/blob/master/docs/translatable.md.</p>"},{"location":"crud/configuration/#setdefaultsort","title":"setDefaultSort","text":"<p><code>setDefaultSort(string $context, string $label, string $direction = 'asc')</code></p> <p>Set the default sort applied in the repository query.</p> <pre><code>$configuration\n -&gt;setDefaultSort('index', 'title', 'asc')\n -&gt;setField('index', 'Title', Field\\TextField::class, [\n 'property' =&gt; 'title',\n 'sort' =&gt; ['title', '.title'],\n ]);\n</code></pre>"},{"location":"crud/configuration/#setissortablecollection","title":"setIsSortableCollection","text":"<p><code>setIsSortableCollection(string $page, bool $isSortableCollection)</code></p> <p>It enables the drag &amp; drop to sort entities.</p> <pre><code>class MyEntity implements EntityInterface\n{\n // ...\n\n /**\n * @ORM\\Column(type=\"integer\", nullable=true)\n */\n private $sortOrder;\n\n public function getSortOrder(): ?int\n {\n return $this-&gt;sortOrder;\n }\n\n public function setSortOrder(?int $sortOrder): self\n {\n $this-&gt;sortOrder = $sortOrder;\n\n return $this;\n }\n\n // ...\n}\n</code></pre>"},{"location":"crud/configuration/#setsortablecollectionproperty","title":"setSortableCollectionProperty","text":"<p><code>setSortableCollectionProperty(string $sortableCollectionProperty)</code></p> <p>In order to sort entities, the default property used is <code>sortOrder</code>. You can set something else.</p>"},{"location":"crud/configuration/#setbatchaction","title":"setBatchAction","text":"<p><code>setBatchAction(string $context, string $action, string $label, callable $callack)</code></p> <p>Add a batch action. The callback has 2 arguments:</p> <ul> <li>An instance of <code>App\\Core\\Entity\\EntityInterface</code></li> <li>An instance of <code>App\\Core\\Manager\\EntityManager</code></li> </ul> <pre><code>use App\\Core\\Entity\\EntityInterface;\nuse App\\Core\\Manager\\EntityManager;\n\n$configuration-&gt;setBatchAction(\n 'index',\n 'delete',\n 'Delete',\n function(EntityInterface $entity, EntityManager $manager) {\n $manager-&gt;delete($entity);\n }\n);\n</code></pre>"},{"location":"crud/configuration/#setglobalbatchaction","title":"setGlobalBatchAction","text":"<p><code>setGlobalBatchAction(string $context, string $action, string $label, callable $callack)</code></p> <p>Add a global batch action. The callback has 3 arguments:</p> <ul> <li>An instance of <code>App\\Core\\Repository\\RepositoryQuery</code></li> <li>An instance of <code>App\\Core\\Manager\\EntityManager</code></li> <li>An array of selected entities or a <code>null</code> value</li> </ul> <p>Do not use the same action in global and classic batch action.</p> <p>The callback can return a response. If not, the user will be redirect automatically. See the example below:</p> <pre><code>use App\\Core\\Entity\\RepositoryQuery;\nuse App\\Core\\Manager\\EntityManager;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\n\n$configuration-&gt;setGlobalBatchAction(\n 'index',\n 'export_json',\n 'Export to JSON',\n function(RepositoryQuery $query, EntityManager $manager, ?array $selection): JsonResponse {\n $items = $selection ?? $query-&gt;find();\n\n return $this-&gt;json($items);\n }\n);\n</code></pre>"},{"location":"crud/generator/","title":"Generator","text":""},{"location":"crud/generator/#prepare-your-entity","title":"Prepare your entity","text":"<ul> <li>implements <code>App\\Core\\Entity\\EntityInterface</code> (see Entity Manager)</li> <li>creates a repository query (see Repository Query)</li> <li>creates a factory (see Factory)</li> <li>generates a form (see <code>php bin/console make:form --help</code>)</li> </ul>"},{"location":"crud/generator/#generation","title":"Generation","text":"<p>The generation is performed in CLI. These information are required:</p> <ul> <li>The name of the futur controller (eg: <code>MyEntityAdminController</code>)</li> <li>The namespace of the entity (eg: <code>MyEntity</code>)</li> <li>The namespace of the entity repository query (eg: <code>MyEntityRepositoryQuery</code>)</li> <li>The namespace of the the entity factory (eg: <code>MyEntityFactory</code>)</li> <li>The namespace of the form used to create and update the entity (eg: <code>MyEntityType</code>)</li> </ul> <p>Simply run <code>php bin/console make:crud-controller</code>.</p>"},{"location":"entities/em/","title":"Entity Manager","text":"<p>The entity manager of Muprh is a proxy of the Doctrine's entity manager. It gives you an easy way to create, update and delete an entity and dispatches events easy to subscribe to.</p>"},{"location":"entities/em/#implementation","title":"Implementation","text":"<p>Entities must implements <code>App\\Core\\Entity\\EntityInterface</code>.</p> src/Entity/MyEntity.php<pre><code>namespace App\\Entity;\n\nuse App\\Repository\\MyEntityRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse App\\Core\\Entity\\EntityInterface;\n\n#[ORM\\Entity(repositoryClass: MyEntityRepository::class)]\nclass MyEntity implements EntityInterface\n{\n // ...\n}\n</code></pre>"},{"location":"entities/em/#usage","title":"Usage","text":"<p>There are 2 entity managers which are services:</p> <ul> <li><code>App\\Core\\Manager\\EntityManager</code> used for all entities</li> <li><code>App\\Core\\Manager\\TranslatableEntityManager</code> used for translatable entities</li> </ul> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Manager\\EntityManager\nuse App\\Entity\\MyEntity;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(EntityManager $entityManager): Response\n {\n $myEntity = new MyEntity();\n\n // Creates an entity\n $entityManager-&gt;create($myEntity);\n\n // Updates an entity\n $entityManager-&gt;update($myEntity);\n\n // Deletes an entity\n $entityManager-&gt;delete($myEntity);\n\n // ...\n }\n}\n</code></pre>"},{"location":"entities/em/#events","title":"Events","text":"<p>Events are dispatched before and after creation, update and deletion. All entities of Muprh use the entity manager.</p> src/EventSubscriber/MyEntityEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\Entity\\EntityInterface;\nuse App\\Core\\Event\\EntityManager\\EntityManagerEvent;\nuse App\\Core\\EventSubscriber\\EntityManagerEventSubscriber;\nuse App\\Entity\\MyEntity;\n\nclass MyEntityEventSubscriber extends EntityManagerEventSubscriber\n{\n public function supports(EntityInterface $entity): bool\n {\n return $entity instanceof MyEntity;\n }\n\n public function onCreate(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n\n public function onUpdate(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n\n public function onDelete(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n\n public function onPreCreate(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n\n public function onPreUpdate(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n\n public function onPreDelete(EntityManagerEvent $event)\n {\n if (!$this-&gt;supports($event-&gt;getEntity())) {\n return;\n }\n\n // ...\n }\n}\n</code></pre>"},{"location":"entities/factory/","title":"Factory","text":"<p>Each entity should have a factory that helps to generate a new entity. A factory must implements <code>App\\Core\\Factory\\FactoryInterface</code>.</p>"},{"location":"entities/factory/#generation","title":"Generation","text":"<p>A factory is basically a service which contain at lease a method named <code>create</code>.</p> <p>The generation is performed in CLI. These information are required:</p> <ul> <li>The name of the futur factory (eg: <code>MyEntityFactory</code>)</li> <li>The namespace of the entity (eg: <code>MyEntity</code>)</li> </ul> <p>Simply run <code>php bin/console make:factory</code>.</p>"},{"location":"entities/factory/#usage","title":"Usage","text":"src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Factory\\MyEntityFactory;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(MyEntityFactory $factory): Response\n {\n $entity = $factory-&gt;create();\n\n // ...\n }\n}\n</code></pre>"},{"location":"entities/query/","title":"Repository Query","text":"<p>A Repository query is an abstraction of the doctrine repository.</p>"},{"location":"entities/query/#requirement","title":"Requirement","text":"<p>Entities must implements <code>App\\Core\\Entity\\EntityInterface</code>.</p> src/Entity/MyEntity.php<pre><code>namespace App\\Entity;\n\nuse App\\Repository\\MyEntityRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse App\\Core\\Entity\\EntityInterface;\n\n#[ORM\\Entity(repositoryClass: MyEntityRepository::class)]\nclass MyEntity implements EntityInterface\n{\n // ...\n}\n</code></pre>"},{"location":"entities/query/#generation","title":"Generation","text":"<p>The generation is performed in CLI. These information are required:</p> <ul> <li>The namespace of the repository (eg: <code>MyEntityRepository</code>)</li> </ul> <p>Simply run <code>php bin/console make:repository-query</code>.</p> <p>Each entity has its own repository query which is a service.</p> src/Repository/MyEntityRepositoryQuery<pre><code>namespace App\\Repository;\n\nuse App\\Core\\Repository\\RepositoryQuery;\nuse Knp\\Component\\Pager\\PaginatorInterface;\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n public function __construct(MyEntityRepository $repository, PaginatorInterface $paginator)\n {\n parent::__construct($repository, 'm', $paginator);\n }\n\n // Example of custom filter\n public function filterByFooBar(bool $foo, bool $bar): self\n {\n $this\n -&gt;andWhere('.foo = :foo')\n -&gt;andWhere('.bar = :bar')\n -&gt;setParameter(':foo', $foo)\n -&gt;setParameter(':bar', $bar);\n\n return $this;\n }\n}\n</code></pre>"},{"location":"entities/query/#usage","title":"Usage","text":"<p>You are able to find entities in an easy way, without knowing the identification variable and without creating a query builder.</p> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Repository\\MyEntityRepositoryQuery\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(MyEntityRepositoryQuery $query): Response\n {\n $entities = $query-&gt;create()-&gt;find();\n $entity = $query-&gt;create()-&gt;findOne();\n\n // Filtered and sorted entities\n $entities = $query-&gt;create()\n -&gt;orderBy('.id', 'DESC')\n -&gt;where('.isPublished = true')\n -&gt;find();\n\n // ...\n }\n}\n</code></pre>"},{"location":"entities/query/#custom-methods","title":"Custom methods","text":"<pre><code>// ...\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n // ...\n\n public function filterByFooBar(bool $foo, bool $bar): self\n {\n $this\n -&gt;andWhere('.foo = :foo')\n -&gt;andWhere('.bar = :bar')\n -&gt;setParameter(':foo', $foo)\n -&gt;setParameter(':bar', $bar);\n\n return $this;\n }\n}\n</code></pre> <pre><code>$entities = $query-&gt;create()\n -&gt;filterByFoo($foo, $bar)\n -&gt;find();\n</code></pre> <p>In the context of a CRUD, filters are applied using the method <code>useFilters</code>. Integers, strings and booleans are automatically processed. Other types are passed to the method <code>filterHandler</code>.</p> <p>You have to override it to manage them, example:</p> <pre><code>// ...\n\nuse App\\Entity\\Something;\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n // ...\n\n public function filterHandler(string $name, $value): self\n {\n if ($name === 'something' &amp;&amp; $value instanceof Something) {\n $this\n -&gt;join('something', 's')\n -&gt;andWhere('s.id = :something')\n -&gt;setParameter('something', $value-&gt;getId())\n ;\n }\n }\n}\n</code></pre> <p>You can also force <code>filterHandler</code> te be used for specific filter field:</p> <pre><code>// ...\n\nclass MyEntityRepositoryQuery extends RepositoryQuery\n{\n public function __construct(Repository $repository, PaginatorInterface $paginator)\n {\n // ...\n\n $this-&gt;addForcedFilterHandler('foo');\n }\n\n public function filterHandler(string $name, $value): self\n {\n // ...\n\n if ($name === 'foo) {\n // ...\n }\n }\n\n}\n</code></pre>"},{"location":"entities/query/#pager","title":"Pager","text":"<p>You can paginate entities (<code>Knp\\Component\\Pager\\Pagination\\PaginationInterface</code>):</p> <pre><code>$pager = $query-&gt;create()-&gt;paginate($page, $maxPerPage);\n</code></pre>"},{"location":"settings/global/","title":"Global settings","text":""},{"location":"settings/global/#create-settings","title":"Create settings","text":"<p>The creation of settings is based on events.</p> <p>Using an event subscriber, you can create settings and define how to edit them. A setting's value is stored in json so a value could be a string, a boolean, an array, etc.</p> <p>See the example below.</p> src/EventSubscriber/SettingEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Setting\\SettingEvent;\nuse App\\Core\\EventSubscriber\\SettingEventSubscriber as EventSubscriber;\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType;\n\nclass SettingEventSubscriber extends EventSubscriber\n{\n protected SettingManager $manager;\n\n public function __construct(SettingManager $manager)\n {\n $this-&gt;manager = $manager;\n }\n\n public function onInit(SettingEvent $event)\n {\n $this-&gt;manager-&gt;init('app_font_color', 'Design', 'Font color', , '#fff');\n $this-&gt;manager-&gt;init('app_background_color', 'Design', 'Background color', '#333');\n $this-&gt;manager-&gt;init('app_maintenance_enabled', 'System', 'Maintenance', false);\n }\n\n public function onFormInit(SettingEvent $event)\n {\n $data = $event-&gt;getData();\n $builder = $data['builder'];\n $entity = $data['entity'];\n\n if (in_array($entity-&gt;getCode(), ['app_font_color', 'app_background_color'])) {\n $builder-&gt;add(\n 'value',\n ColorType::class,\n [\n 'label' =&gt; $entity-&gt;getLabel(),\n ]\n );\n }\n\n if (in_array($entity-&gt;getCode(), ['app_maintenance_enabled'])) {\n $builder-&gt;add(\n 'value',\n CheckboxType::class,\n [\n 'label' =&gt; $entity-&gt;getLabel(),\n ]\n );\n }\n }\n}\n</code></pre> <p>Result:</p> <p></p>"},{"location":"settings/global/#access-settings","title":"Access settings","text":"<p>Settings are accessible using <code>App\\Core\\Setting\\SettingManager</code> which is a service.</p> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(SettingManager $settingManager): Response\n {\n $fontColor = $settingManager-&gt;get('app_font_color');\n $backgroundColor = $settingManager-&gt;get('app_background_color');\n $maintenanceEnabled = $settingManager-&gt;get('app_maintenance_enabled');\n\n // ...\n }\n}\n</code></pre> <p>In a template, you can use the function <code>setting</code>:</p> <pre><code>Font color: {{ setting('app_font_color') }}&lt;br&gt;\nBackground color: {{ setting('app_background_color') }}&lt;br&gt;\nMaintenance enabled: {{ setting('app_maintenance_enabled') ? 'Yes' : 'No' }}&lt;br&gt;\n</code></pre>"},{"location":"settings/global/#update-settings","title":"Update settings","text":"<p>Settings are accessible using <code>App\\Core\\Setting\\SettingManager</code> which is a service.</p> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Setting\\SettingManager;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(SettingManager $settingManager): Response\n {\n $settingManager-&gt;set('app_font_color', '#f00');\n $settingManager-&gt;set('app_background_color', '#00f');\n $settingManager-&gt;set('app_maintenance_enabled', true);\n\n // ...\n }\n}\n</code></pre> <p>You can also edit them from UI:</p> <p></p>"},{"location":"settings/global/#options","title":"Options","text":"<p>You can add options using this way:</p> <pre><code>$event-&gt;setOption('view', 'large');\n</code></pre> <p>Available options:</p> <ul> <li><code>view</code> (default: <code>false</code>): show a large modal</li> </ul>"},{"location":"settings/navigation/","title":"Navigation settings","text":""},{"location":"settings/navigation/#create-settings","title":"Create settings","text":"<p>The creation of settings is based on events.</p> <p>Using an event subscriber, you can create settings and define how to edit them. A setting's value is stored in json so a value could be a string, a boolean, an array, etc.</p> <p>See the example below.</p> <p>src/EventSubscriber/NavigationSettingEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Setting\\NavigationSettingEvent;\nuse App\\Core\\EventSubscriber\\NavigationSettingEventSubscriber as EventSubscriber;\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType;\n\nclass NavigationSettingEventSubscriber extends EventSubscriber\n{\n protected NavigationSettingManager $manager;\n\n public function __construct(NavigationSettingManager $manager)\n {\n $this-&gt;manager = $manager;\n }\n\n public function onInit(NavigationSettingEvent $event)\n {\n $data = $event-&gt;getData();\n $navigation = $data['navigation'];\n\n $this-&gt;manager-&gt;init($navigation, 'nav_tracker_code', 'Stats', 'Tracker', '');\n $this-&gt;manager-&gt;init($navigation, 'nav_contact_email', 'Contact', 'Email', 'foo@example.com');\n }\n\n public function onFormInit(NavigationSettingEvent $event)\n {\n $data = $event-&gt;getData();\n $builder = $data['builder'];\n $entity = $data['entity'];\n\n if (in_array($entity-&gt;getCode(), ['nav_tracker_code'])) {\n $builder-&gt;add(\n 'value',\n TextType::class,\n [\n 'label' =&gt; $entity-&gt;getLabel(),\n ]\n );\n }\n\n if (in_array($entity-&gt;getCode(), ['nav_contact_email'])) {\n $builder-&gt;add(\n 'value',\n EmailType::class,\n [\n 'label' =&gt; $entity-&gt;getLabel(),\n ]\n );\n }\n }\n}\n</code></pre> Result:</p> <p></p>"},{"location":"settings/navigation/#access-settings","title":"Access settings","text":"<p>Settings are accessible using <code>App\\Core\\Setting\\NavigationSettingManager</code> which is a service.</p> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse App\\Core\\Site\\SiteRequest;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response\n {\n $trackerCode = $settingManager-&gt;get($siteRequest-&gt;getNavigation(), 'nav_tracker_code');\n $contactEmail = $settingManager-&gt;get('my_nav', 'nav_contact_email');\n\n // ...\n }\n}\n</code></pre> <p>In a template, you can use the function <code>navigation_setting</code>:</p> <pre><code>Tracker code: {{ navigation_setting(_navigation, 'nav_tracker_code') }}&lt;br&gt;\nContact email: {{ navigation_setting('my_nav', 'nav_contact_email') }}&lt;br&gt;\n</code></pre>"},{"location":"settings/navigation/#update-settings","title":"Update settings","text":"<p>Settings are accessible using <code>App\\Core\\Setting\\NavigationSettingManager</code> which is a service.</p> src/Controller/FooController.php<pre><code>namespace App\\Controller;\n\nuse App\\Core\\Setting\\NavigationSettingManager;\nuse App\\Core\\Site\\SiteRequest;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass FooController\n{\n public function foo(NavigationSettingManager $settingManager, SiteRequest $siteRequest): Response\n {\n $settingManager-&gt;set($siteRequest-&gt;getNavigation(), 'nav_tracker_code', '...');\n $settingManager-&gt;set('my_nav', 'nav_contact_email', '...');\n\n // ...\n }\n}\n</code></pre> <p>You can also edit them from UI:</p> <p></p>"},{"location":"tree/","title":"Tree manager","text":"<p>Murph manages contents this way:</p> <ul> <li>You define navigations</li> <li>For each navigation, you define menus</li> <li>For each menu, you define nodes (elements)</li> <li>and for each node, you define:<ul> <li>an optional page</li> <li>the routing</li> <li>some attributes</li> <li>a sitemap configuration</li> </ul> </li> </ul>"},{"location":"tree/#diagram","title":"Diagram","text":"<pre><code>%%{\n init: {\n \"theme\": \"dark\",\n \"flowchart\": {\n \"curve\": \"cardinal\"\n }\n }\n}%%\n\ngraph TB\n N1[Navigation 1] --&gt; M1[Menu 1];\n N2[Navigation ...];\n NX[Navigation N];\n\n N1 --&gt; M2[Menu ...];\n N1 --&gt; MX[Menu N];\n\n N2 --&gt; L1[...]\n NX --&gt; L2[...]\n\n M1 --&gt; MN1[Node 1]\n M1 --&gt; MN2[Node ...]\n M1 --&gt; MN3[Node N]\n\n M2 --&gt; L3[...]\n MX --&gt; L4[...]\n\n MN1 --&gt; P1[Page]\n\n P1 --&gt; B1[Block 1]\n P1 --&gt; B2[Block ...]\n P1 --&gt; BN[Block N]</code></pre>"},{"location":"tree/menu/","title":"Menu","text":"<p>To create a menu, go to <code>Trees</code>, select the navigation and click on <code>Add a menu</code>. Then fill the form and save.</p> <ul> <li>The <code>label</code> is the label displayed whenever necessary (eg: <code>Top menu</code>)</li> <li>The <code>code</code> is an unique technical identifier (in the given navigation) and it is useful in templating, routing and settings (eg: <code>top</code>)</li> </ul> <p>When a menu is created then an node is automatically generated.</p> <p></p>"},{"location":"tree/navigation/","title":"Navigation","text":"<p>To create a navigation, go to <code>Navigations</code> and click on <code>New</code>. Then fill the form and save.</p> <ul> <li>The <code>label</code> is the label displayed whenever necessary (eg: <code>Example</code>)</li> <li>The <code>locale</code> is the language used in the content (eg: <code>en</code>)</li> <li>The <code>code</code> is a unique technical identifier useful in templating, routing and settings (eg: <code>example_en</code>)</li> <li>The <code>domain</code> defines the main domain used to access the navigation (eg: <code>example.com</code>)</li> <li><code>Additional domains</code> are additional domains used to access the navigation (eg: <code>www.example.com</code>). You can specify regular expression to match all that you want</li> </ul> <p>If several navigations share the same domain, then the locale will by used to prefix routes. But if a navigation uses a single domain then the local will not prefix routes.</p> <p></p>"},{"location":"tree/node/","title":"Node","text":"<p>A node allows you to create a tree structure in a menu. To create or update a node, click on \"Edit\" or the sign \"+\". The basic information to fill is <code>label</code> but more parameters are accessible via 4 tabs:</p> <ul> <li>Content</li> <li>Routing</li> <li>Attributes</li> <li>Sitemap</li> </ul>"},{"location":"tree/node/#content","title":"Content","text":"<p>The content tab allow you to define an optional <code>Page</code> to associate to the node. You can also define that the node is an alias of another node.</p>"},{"location":"tree/node/#routing","title":"Routing","text":"<p>The routing tab is very important. It allows you to define all parameters related to:</p> <ul> <li>The routing (obviously):<ul> <li>An optional URL with parameters</li> <li>A route name generated using the field <code>code</code></li> </ul> </li> <li>A custom content-type of the response (eg: 'text/plain')</li> <li>A custom controller called when the node is requested</li> </ul> <p>To add a controller in the list, edit <code>config/packages/app.yaml</code>:</p> <pre><code>core:\n site:\n controllers:\n - {name: 'Foo', action: 'App\\Controller\\ExampleController::foo'}\n - {name: 'Bar', action: 'App\\Controller\\OtherController::bar'}\n</code></pre> <p>If you need to restrict the access, you can provided a list of roles in the configuration:</p> config/packages/app.yaml<pre><code>core:\n site:\n security:\n roles:\n - {name: 'Foo role', role: 'ROLE_FOO'}\n - {name: 'Bar role', role: 'ROLE_BAR'}\n</code></pre> <p>Then you will be able to select what roles are required:</p> <p></p>"},{"location":"tree/node/#attributes","title":"Attributes","text":"<p>Attributes are a collection of keys and values attached to a node (eg: class, icon, whatever you want).</p>"},{"location":"tree/node/#sitemap","title":"Sitemap","text":"<p>This tab contains information to configure the sitemap.</p>"},{"location":"tree/page/","title":"Page","text":"<p>A page is a doctrine entity that contains blocks and form builder. You can run <code>php bin/console make:page</code> and generate a new page in an interactive way.</p> src/Entity/Page/YourPage.php<pre><code>namespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Entity\\Site\\Page\\Page;\nuse App\\Core\\Form\\Site\\Page\\TextBlockType;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myBlock',\n TextBlockType::class,\n [\n 'label' =&gt; 'My block',\n 'options' =&gt; [\n // options given to the sub form\n ],\n ]\n );\n\n // ...\n }\n\n public function setMyBlock(Block $block)\n {\n return $this-&gt;setBlock($block);\n }\n\n public function getMyBlock(): Block\n {\n return $this-&gt;getBlock('myBlock');\n }\n\n // ...\n}\n</code></pre> <p>Then edit <code>config/packages/app.yaml</code> and add your page:</p> <pre><code>core:\n site:\n pages:\n App\\Entity\\Page\\SimplePage:\n name: 'Simple page'\n templates:\n - {name: \"Default\", file: \"page/simple/default.html.twig\"}\n App\\Entity\\Page\\YourPage:\n name: 'Your page'\n templates:\n - {name: \"Template 1\", file: \"page/your_page/template1.html.twig\"}\n - {name: \"Template 2\", file: \"page/your_page/template2.html.twig\"}\n</code></pre>"},{"location":"tree/page/#blocks","title":"Blocks","text":""},{"location":"tree/page/#textblocktype","title":"TextBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\TextBlockType</code> will render a symfony <code>TextType</code>.</p>"},{"location":"tree/page/#textareablocktype","title":"TextareaBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\TextareaBlockType</code> will render a symfony <code>TextareaType</code>.</p>"},{"location":"tree/page/#choiceblocktype","title":"ChoiceBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\ChoiceBlockType</code> will render a symfony <code>ChoiceType</code>.</p>"},{"location":"tree/page/#fileblocktype","title":"FileBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\FileBlockType</code> will render a symfony <code>FileType</code> with a download link.</p> <p>In the getter, you must specify the block:</p> <pre><code>use App\\Core\\Entity\\Site\\Page\\FileBlock;\n\npublic function getMyBlock(): Block\n{\n return $this-&gt;getBlock('myBlock', FileBlock::class);\n}\n</code></pre>"},{"location":"tree/page/#filepickerblocktype","title":"FilePickerBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\FilePickerBlockType</code> will render a specific widget that use the file manager.</p>"},{"location":"tree/page/#editorjstextareablocktype","title":"EditorJsTextareaBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\EditorJsTextareaBlockType</code> will render a EditorJs widget.</p>"},{"location":"tree/page/#grapesjsblocktype","title":"GrapesJsBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\GrapesJsBlockType</code> will render a GrapesJS editor.</p>"},{"location":"tree/page/#tinymcetextareablocktype","title":"TinymceTextareaBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\TinymceTextareaBlockType</code> will render a Tinymce editor.</p>"},{"location":"tree/page/#imageblocktype","title":"ImageBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\ImageBlockType</code> will render a symfony <code>FileType</code> with a preview of the image.</p> <p>In the getter, you must specify the block:</p> <pre><code>use App\\Core\\Entity\\Site\\Page\\FileBlock;\n\npublic function getMyBlock(): Block\n{\n return $this-&gt;getBlock('myBlock', FileBlock::class);\n}\n</code></pre>"},{"location":"tree/page/#collectionblocktype","title":"CollectionBlockType","text":"<p><code>App\\Core\\Form\\Site\\Page\\CollectionBlockType</code> will a render a symfony <code>CollectionType</code> with availabity to add and remove elements.</p> <pre><code>use App\\Core\\Entity\\Site\\Page\\CollectionBlock;\n\npublic function getMyBlock(): Block\n{\n return $this-&gt;getBlock('myBlock', CollectionBlock::class);\n}\n</code></pre>"},{"location":"tree/page/#event","title":"Event","text":"<p>When a page is being edited, the options can be set as follows:</p> src/EventSubscriber/PageEventSubscriber.php<pre><code>namespace App\\EventSubscriber;\n\nuse App\\Core\\Event\\Page\\PageEditEvent;\nuse App\\Entity\\Page\\YourPage;\n\nclass PageEventSubscriber implements EventSubscriberInterface\n{\n public static function getSubscribedEvents()\n {\n return [\n PageEditEvent::FORM_INIT_EVENT =&gt; ['onFormInit'],\n ];\n }\n\n public function onFormInit(PageEditEvent $event)\n {\n if ($event-&gt;getPage() instanceof YourPage) {\n $event-&gt;addPageBuilderOptions([\n // options\n ]);\n }\n }\n}\n</code></pre>"},{"location":"utils/cache/","title":"Cache Manager","text":"<ul> <li>Service: <code>App\\Core\\Cache\\SymfonyCacheManager</code></li> <li>Methods:<ul> <li><code>cleanRouting()</code>: clear the cache of routes</li> <li><code>cleanAll(OutputInterface $output = null)</code>: clean and warmup all cache</li> </ul> </li> </ul>"},{"location":"utils/doctrine/","title":"Doctrine","text":""},{"location":"utils/doctrine/#timestampable","title":"Timestampable","text":"<p><code>App\\Core\\Doctrine\\Timestampable</code> is a trait usuble in an entity. It adds <code>createdAt and updatedAt</code> datetime attributes with the setters and the getters :</p> <ul> <li><code>setCreatedAt(?\\DateTime $createdAt): self</code></li> <li><code>setUpdated(?\\DateTime $createdAt): self</code></li> <li><code>getCreatedAt(): ?\\DateTime</code></li> <li><code>getUpdatedAt(): ?\\DateTime</code></li> </ul> <p>When the entity is created or updated, <code>createdAt</code> and <code>updatedAt</code> are automatically updated to.</p>"},{"location":"utils/doctrine/#usage","title":"Usage","text":"src/Entity/FooEntity.php<pre><code>namespace App/Entity;\n\nuse use App\\Core\\Entity\\EntityInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\n\nuse App\\Core\\Doctrine\\Timestampable;\nuse App\\Core\\Entity\\EntityInterface;\nuse App\\Repository\\FooRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity(repositoryClass: FooRepository::class)]\n#[ORM\\HasLifecycleCallbacks]\nclass FooEntity implements EntityInterface\n{\n use Timestampable;\n\n // ...\n}\n</code></pre>"},{"location":"utils/file_attribute/","title":"File attribute","text":"<p><code>App\\Core\\File\\FileAttribute::handleFile</code> transforms a file path to an instance of <code>Symfony\\Component\\HttpFoundation\\File\\File</code>. You can specify another class if needed. If the path is <code>null</code> or if the file does not exist, it returns <code>null</code>.</p> <pre><code>use App\\Core\\File\\FileAttribute;\nuse App\\Foo\\Bar;\n\n$path = 'path/to/file';\n$file = FileAttribute::handleFile($path); // returns an instance of File\n\n$path = 'path/to/file';\n$file = FileAttribute::handleFile($path, Bar::class); // returns an instance of Bar\n\n$path = 'path/to/removed_file';\n$file = FileAttribute::handleFile($path); // returns null\n\n$path = null;\n$file = FileAttribute::handleFile($path); // returns null\n</code></pre>"},{"location":"utils/file_handler/","title":"File upload handler","text":"<p><code>App\\Core\\Form\\FileUploadHandler</code> is a service and helps you to upload a file. See example below.</p> <pre><code>use App\\Core\\Form\\FileUploadHandler;\nuse App\\Entity\\Foo;\nuse App\\Form\\FooType;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\npublic function upload(Request $request, FileUploadHandler $fileUpload)\n{\n $entity = new Foo();\n $form = $this-&gt;createForm(FooType::class, $foo);\n\n if ($request-&gt;isMethod('POST')) {\n $form-&gt;handleRequest($request);\n\n if ($form-&gt;isValid()) {\n $fileDirectory = 'uploads/';\n $keepOriginalFilename = false;\n\n $fileUpload-&gt;handleForm(\n uploadedFile: $form-&gt;get('image')-&gt;getData(), // Symfony\\Component\\HttpFoundation\\File\\UploadedFile or null\n path: $fileDirectory,\n // optional\n afterUploadCallback: function ($filename) use ($entity, $fileDirectory) {\n $entity-&gt;setImage($fileDirectory.$filename);\n },\n // optional\n keepOriginalFilename: $keepOriginalFilename\n );\n\n // ...\n }\n }\n}\n</code></pre> <p>If you need to generate custom filenames, <code>FileUploadHandler</code> allows you to define a generator:</p> <pre><code>use Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\n\n$fileUpload-&gt;setFilenameGenerator(function(UploadedFile $file) {\n return sprintf('%d.%s', mt_rand(), $file-&gt;guessExtension());\n});\n</code></pre>"},{"location":"utils/mail/","title":"Mail","text":"<p><code>App\\Core\\Notification\\MailNotifier</code> is a service and helps you to create a mail using twig template.</p> <p>Useful methods:</p> <ul> <li><code>setRecipients(array $recipients): MailNotifier</code></li> <li><code>setBccRecipients(array $bccRecipients): MailNotifier</code></li> <li><code>setSubject(?string $subject): MailNotifier</code></li> <li><code>setFrom($from): MailNotifier</code></li> <li><code>setReplyTo($replyTo): MailNotifier</code></li> <li><code>setAttachments(array $attachments): MailNotifier</code></li> <li><code>addRecipient(string $email, bool $isBcc = false): MailNotifier</code></li> <li><code>addRecipients(array $emails, bool $isBcc = false): MailNotifier</code></li> <li><code>addRecipientByUser(\\App\\Entity\\User $user, bool $isBcc = false): self</code></li> <li><code>addRecipientsByUsers($users, bool $isBcc = false): self</code></li> <li><code>addAttachment(string $attachment): MailNotifier</code></li> <li><code>addAttachments(array $attachments): MailNotifier</code></li> <li><code>init(): MailNotifier</code></li> <li><code>notify(string $template, array $data = [], string $type = 'text/html'): MailNotifier</code></li> </ul> <p>Exemple:</p> <pre><code>use App\\Core\\Notification\\MailNotifier;\nuse App\\Repository\\UserRepositoryQuery;\n\npublic function foo(MailNotifier $notifier, UserRepositoryQuery $query)\n{\n // ...\n\n $notifier\n -&gt;init()\n -&gt;setSubject('Your bill')\n -&gt;addRecipient('john.doe@example.com')\n -&gt;addRecipients(array_map(\n fn($u) =&gt; $u-&gt;getEmail(),\n $query-&gt;create()-&gt;where('.isAdmin = true')-&gt;find()\n ), true)\n -&gt;addAttachment('path/to/bill.pdf')\n -&gt;notify('mail/bill.html.twig', [\n // view params\n ])\n ;\n}\n</code></pre>"},{"location":"utils/slug/","title":"Slug","text":"<p>Murph requires <code>cocur/slugify</code>. See the official documentation on Github.</p>"},{"location":"utils/editors/editorjs/","title":"Editor.js","text":"<p>Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor's Core.</p> <p>Editor.js is fully integrated in Murph as form types.</p> <p></p>"},{"location":"utils/editors/editorjs/#classic-form","title":"Classic form","text":"<pre><code>// src/Form/ExampleType.php\nnamespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\EditorJsTextareaType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myField',\n EditorJsTextareaType::class\n );\n\n // ...\n }\n\n // ...\n}\n</code></pre> <p>Modified data should return stringified JSON array if empty:</p> <pre><code>public function getMyField(): string\n{\n if (empty($this-&gt;myField)) {\n $this-&gt;myField = '[]';\n }\n\n return $this-&gt;myField;\n}\n</code></pre>"},{"location":"utils/editors/editorjs/#page-form","title":"Page form","text":"<pre><code>// src/Entity/Page/YourPage.php\nnamespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\EditorJsTextareaBlockType;\n\n#[@ORM\\Entity]\nclass YourPage extends Page\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myBlock',\n EditorJsTextareaBlockType::class,\n [\n 'label' =&gt; 'My block',\n 'row_attr' =&gt; [\n ],\n 'options' =&gt; [\n // options given to the sub form\n ],\n ]\n );\n\n // ...\n }\n\n public function setMyBlock(Block $block)\n {\n return $this-&gt;setBlock($block);\n }\n\n public function getMyBlock(): Block\n {\n return $this-&gt;getBlock('myBlock');\n }\n\n // ...\n}\n</code></pre>"},{"location":"utils/editors/editorjs/#rendering","title":"Rendering","text":"<p>Editor.js will generate a JSON which contains blocks.</p> <p>Supported blocks:</p> <ul> <li>paragraph</li> <li>header</li> <li>quote</li> <li>delimiter</li> <li>warning</li> <li>list</li> <li>nestedList</li> <li>checkList</li> <li>table</li> <li>code</li> <li>raw</li> <li>image</li> <li>link</li> </ul> <p>To render HTML, the basic way is: <code>{{ value|editorjs_to_html }}</code> If you want to render specific blocks: <code>{{ value|editorjs_to_html(['paragraph', 'header', ...])) }}</code></p> <p>Block have default templates stored in <code>vendor/murph/murph-core/src/core/Resources/views/editorjs</code>. They can be simply overridden in <code>config/packages/app.yaml</code>:</p> <pre><code>core:\n editor_js:\n blocks:\n paragraph: 'path/to/paragraph.html.twig'\n header: 'path/to/header.html.twig'\n</code></pre>"},{"location":"utils/editors/grapesjs/","title":"GrapesJS","text":"<p>GrapesJS is a web builder which combines different tools and features with the goal to help users to build HTML templates without any knowledge of coding. It's a very good solution to replace the common WYSIWYG editor like TinyMCE.</p> <p>GrapesJS is fully integrated in Murph as form types.</p> <p></p>"},{"location":"utils/editors/grapesjs/#classic-form","title":"Classic form","text":"<pre><code>// src/Form/ExampleType.php\nnamespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\GrapesJsType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myField',\n GrapesJsType::class\n );\n\n // ...\n }\n\n // ...\n}\n</code></pre>"},{"location":"utils/editors/grapesjs/#page-form","title":"Page form","text":"<pre><code>// src/Entity/Page/YourPage.php\nnamespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\GrapesJsBlockType;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myBlock',\n GrapesJsBlockType::class,\n [\n 'label' =&gt; 'My block',\n 'row_attr' =&gt; [\n ],\n 'options' =&gt; [\n // options given to the sub form\n ],\n ]\n );\n\n // ...\n }\n\n public function setMyBlock(Block $block)\n {\n return $this-&gt;setBlock($block);\n }\n\n public function getMyBlock(): Block\n {\n return $this-&gt;getBlock('myBlock');\n }\n\n // ...\n}\n</code></pre>"},{"location":"utils/editors/grapesjs/#options","title":"Options","text":"<p>There are 3 modes:</p> <ul> <li>Bootstrap 4 (default): <code>bootstrap4</code></li> <li>Preset webpage: <code>presetWebpage</code></li> <li>Preset newsletter: <code>presetNewsletter</code></li> </ul> <p>To specify a mode, you must define the attribute <code>data-grapesjs</code>:</p> src/Form/ExampleType.php<pre><code>$builder-&gt;add(\n 'myField',\n GrapesJsType::class,\n [\n // ...\n\n 'attr' =&gt; [\n 'data-grapesjs' =&gt; 'bootstrap4',\n ],\n ]\n);\n\n```php-inline title=\"src/Entity/Page/YourPage.php\"\n$builder-&gt;add(\n 'myBlock',\n GrapesJsBlockType::class,\n [\n // ...\n\n 'options' =&gt; [\n 'attr' =&gt; [\n 'data-grapesjs' =&gt; 'bootstrap4',\n ],\n ],\n ]\n);\n</code></pre>"},{"location":"utils/editors/grapesjs/#rendering","title":"Rendering","text":"<p>GrapesJS will generate a JSON which contains HTML and CSS.</p> <ul> <li>To extract HTML: <code>{% set html = value|grapesjs_html %}</code></li> <li>To extract CSS: <code>{% set css = value|grapesjs_css %}</code></li> </ul> <p>Depending of the mode, you will need to import in the app sass file:</p> <ul> <li>Bootstrap 4: <code>@import \"~bootstrap/scss/bootstrap.scss\";</code></li> <li>Preset webpage: <code>@import \"~grapesjs-preset-webpage/dist/grapesjs-preset-webpage.min.css\";</code></li> <li>Preset newsletter: <code>@import \"~grapesjs-preset-newsletter/dist/grapesjs-preset-newsletter.css\";</code></li> </ul>"},{"location":"utils/editors/tinymce/","title":"TinyMCE","text":"<p>TinyMCE gives you total control over your rich text editing. It's fully integrated in Murph as form types.</p> <p></p>"},{"location":"utils/editors/tinymce/#classic-form","title":"Classic form","text":"src/Form/ExampleType.php<pre><code>namespace App\\Form\\ExampleType;\n\nuse App\\Core\\Form\\Type\\TinymceTextareaType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass ExampleType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myField',\n TinymceTextareaType::class\n );\n\n // ...\n }\n\n // ...\n}\n</code></pre>"},{"location":"utils/editors/tinymce/#page-form","title":"Page form","text":"src/Entity/Page/YourPage.php<pre><code>namespace App\\Entity\\Page;\n\nuse App\\Core\\Entity\\Site\\Page\\Block;\nuse App\\Core\\Form\\Site\\Page\\TinymceTextareaBlockType;\n\n#[ORM\\Entity]\nclass YourPage extends Page\n{\n public function buildForm(FormBuilderInterface $builder, array $options)\n {\n $builder-&gt;add(\n 'myBlock',\n TinymceTextareaBlockType::class,\n [\n 'label' =&gt; 'My block',\n 'row_attr' =&gt; [\n ],\n 'options' =&gt; [\n // options given to the sub form\n ],\n ]\n );\n\n // ...\n }\n\n public function setMyBlock(Block $block)\n {\n return $this-&gt;setBlock($block);\n }\n\n public function getMyBlock(): Block\n {\n return $this-&gt;getBlock('myBlock');\n }\n\n // ...\n}\n</code></pre>"},{"location":"utils/editors/tinymce/#options","title":"Options","text":"<p>There are 2 predefined modes:</p> <ul> <li>Default: <code>default</code></li> <li>Light: <code>light</code></li> </ul> <p>To specify a mode, you must define the attribute <code>data-tinymce</code>:</p> src/Form/ExampleType.php<pre><code>$builder-&gt;add(\n 'myField',\n TinymceTextareaType::class,\n [\n // ...\n\n 'attr' =&gt; [\n 'data-tinymce' =&gt; 'light',\n ],\n ]\n);\n</code></pre> src/Entity/Page/YourPage.php<pre><code>$builder-&gt;add(\n 'myBlock',\n TinymceTextareaBlockType::class,\n [\n // ...\n\n 'options' =&gt; [\n 'attr' =&gt; [\n 'data-tinymce' =&gt; 'light',\n ],\n ],\n ]\n);\n</code></pre> <p>To custom the editor, see the example below:</p> assets/js/admin.js<pre><code>import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'\n\nwindow.tinymce.language = 'fr_FR'\n\nwindow.tinymceModes = {\n myCustomMode: {\n plugins: '...',\n menubar: '...',\n toolbar: '...'\n quickbars_selection_toolbar: '...'\n contextmenu: '...'\n templates: [\n {\n title: 'Container',\n description: 'Add a bootstrap container',\n content: '&lt;div class=\"container\"&gt;&lt;div class=\"selcontent\"&gt;&lt;/div&gt;&lt;/div&gt;'\n }\n // ...\n ],\n content_style: '...'\n }\n}\n</code></pre> src/Form/ExampleType.php<pre><code>$builder-&gt;add(\n 'myField',\n TinymceTextareaType::class,\n [\n // ...\n\n 'attr' =&gt; [\n 'data-tinymce' =&gt; 'myCustomMode',\n ],\n ]\n);\n</code></pre> src/Entity/Page/YourPage.php<pre><code>$builder-&gt;add(\n 'myBlock',\n TinymceTextareaBlockType::class,\n [\n // ...\n\n 'options' =&gt; [\n 'attr' =&gt; [\n 'data-tinymce' =&gt; 'myCustomMode',\n ],\n ],\n ]\n);\n</code></pre>"},{"location":"utils/editors/tinymce/#rendering","title":"Rendering","text":"<p>TinyMCE generates HTML. To render, simply use <code>{{ value|raw }}</code>.</p>"},{"location":"utils/form/collection/","title":"Collection","text":"<p>When you need to manage a collection in a form, you can use <code>App\\Core\\Form\\Type\\CollectionType</code> with these options:</p> Option Type Default Description <code>collection_name</code> <code>string</code> <code>null</code> Unique name of the collection <code>label_add</code> <code>string</code> <code>Add</code> Unique name of the collection <code>label_delete</code> <code>string</code> <code>Delete</code> Unique name of the collection <code>template_before_item</code> <code>string</code> <code>null</code> A template included before an existing itam of the collection <code>template_after_item</code> <code>string</code> <code>null</code> A template included after an existing itam of the collection <p>...and and all options of a symfony collection type.</p>"},{"location":"utils/form/file_picker/","title":"File picker","text":"<p>Murph provides a file picker using the file manager system: <code>App\\Core\\Form\\FileManager\\FilePickerType</code>. It allows you to pick an existing file or upload it. After saving, the file is previewed if possible.</p>"}]}