-
Posts
1,331 -
Joined
-
Last visited
-
Days Won
61
Everything posted by BitPoet
-
The CRM part some highly specific stuff, certainly not helpful for anybody else. The modules I'll focus on getting into publishable state will be OpenSearchServerSearch (including the schema for OSS), PagePublishPerLanguage, ArticleIDLinks and ActiveDirectory/LDAP integration. If there's time left (I'm a bit cautious with promises atm, as I'm going to be rather involved in switching our ERP system next year), I'll see if I can change the translation TextFormatter into something generic and configurable and bundle everything together with the notice board into an Intranet site profile.
-
Install to server subdirectory path not working with PW 2.7?
BitPoet replied to nixsofar's topic in Getting Started
That's hard to say. I'd have a look in your browser's developer console if there's a redirect of some kind going on (or not) that causes the problem, then look into the server logs to see if something jumps your eyes. -
Untested, but every multi language field class should implement FieldtypeLanguageInterface, which you should be able to check for using instanceof. if($f->type instanceof FieldtypeLanguageInterface) $langFields->add($f);
-
It's been more than 9 months since I wrote in this thread about our intranet. Well, things happened that had me away for a good while, and when I got back to it, the wishlist exploded. Finally, though, the migrated intranet is up and running, and based on the feedback I'm getting, I can already call it a success. Here's a short feature list: Running on Windows Server 2012R2 / IIS 8.5 A little over 8000 pages, two languages, 600+ frontend users in 15 countries, 45 editors About 20 templates 15 custom modules Reduced custom CRM module code to less than one-eighth (counted in bytes, not lines) Integrated web server authentication and detailed Group permissions using Apeisa's UserGroups module (big thanks there! Keeping our group-based permissions was one of the main requirements for the new CMS) Live user + groups synchronization from Active Directory Granular search via OpenSearchServer (including permissions handling and indexing linked PageFiles) Tag based in-text translation system through custom Textformatter module linked to our ERP database, with own CKEdtior plugin (think detailed technical data sheets where you don't want to edit every language version separately) About 400 dynamic pages that range from simple database-driven lists to rich single page apps Persistent page links using page IDs that get replaced on save + render Versioning for textareas Custom links in the style of "article:CUSTOM_UNIQUE_FIELD" used to fetch recurring content into pages in our in-house manuals Booking system for our inhouse training center using just pages, standard fields and a bit (well, a good bit) of frontend-editing glue, with graphical interactive calendar Pages can be published per language through a multilanguage checkbox, with corresponding indicator icons and actions in ProcessPageList Notice board system (title, short body, optional attachment, expiry date) for main and department pages, with frontend adding/editing and admin interface Detailed permissions reports in backend and through command line for compliance audits Not yet implemented, but a working prototype is already on the test system: a simple question/linear response forum system with rating, tagging, answer accepting and highlighting options, primarily targeted towards international knowledge exchange Big thanks to Ryan, PW is really an outstanding system in terms of flexibility, scaling and ease of integration, and also to everyone who contributed in any way to PW itself or its huge module repository. As a funny side node, I talked to a colleague from manufacturing when migration was still under way, and he was initially a bit miffed that I'd swap the system he already knows (hey, it's only been 7 1/2 years!) - but only until I told him I'd use PW. At the mention of the word "Process" he immediately determined that his workflow to add content would get a lot smoother, something I could confirm without thinking. (The old system had a separation between categories and articles, so you had to create both in different backend screens to get a navigation entry for a simple page that shows just some text - in PW he only needs to tick the "Show in Navigation" checkbox for his new page.) Shows that the right naming choice already gets you halfway to the goal Now that "The Big Project" is done, I'll only have to weather the usual end-of-year-craziness, then I'll get my custom modules into a shape where I can share them (meaning: polish documentation, add reasonable defaults and switch to module config fields instead of config files).
- 111 replies
-
- 14
-
-
After having implemented a few different event dispatching systems in my programming carreer, I've still not found the perfect approach, but I've very much given up on overly declarative ways (which do include centralized event management) because at some point, they tend to introduce a level of coding ugliness that's without second. Where, in PW, I can add a hook the moment before it is needed if circumstances are right, a declarative approach often needs loads and loads of repetitive code inside the event hook to make sure its logic is only executed when necessary. Also, declarative event syntax tends to necessitate carrying around much more context, which can be detrimental to memory consumption and performance. Mediator patterns are good in theory, but everyone who has worked on huge event driven projects in C# or Java knows that it's not just a few lines of code more - if you're consequent, you're going to be writing a few times as much code and still find yourself making assumptions about needed interfaces that aren't matched by reality. To me, the biggest issues I have with PW's hook system are ones that can be solved without taking a wholly new approach: Hookability: there are still too many unhookable methods, but that's often because of the next point on my list, which is: Granularity: like with all growing codebases, often code gets written that "gets the job done", yet it does more than one thing at once. Most headaches I get arise from the fact that the hook I need would have to be called somewhere in the middle of an existing method. Also, for debugging reasons, wrapping individual changes inside their own, aptly named hookable method would go a long way; Statefulness: not every state transition has a hook, and if it is there, some information about what the state change(s) entailed may be missing at the next hookable point, or not convenient to get at, and even more, it's often difficult to discern how to revert the state transition. These are usually small things in the scale of PW (and happen far less than in other frameworks I've worked with) as a whole but will pop up every so often as the codebase evolves and grows. No event system will catch that on its own either; I was about to write "hook execution order" before I felt a murky memory tickle the back of my mind and decided to take a look to make sure - lo and behold, hook priority has already been there for years. Yay, Ryan! This is no criticism on PW btw., just a reflection of what points keep popping up. Like I wrote, these are the same things that come up in every big and growing software project. I'm very much with owzim that the only way around debugging madness is granularity. No event system will cure you of memory exhaustion or eye soreness when the backtraces keep growing and the objects to inspect are deeply nested. Once runkit_method_add or a similar mechanism becomes stable, it might make sense for debugging purposes to (optionally) augment the Wire base class (or an event manager stand-in singleton) with static hook closure methods following a fixed naming theme that a debugger can look at, which would alleviate one of the main issues in the OP.
-
PagePathHistory with multi-language page names
BitPoet replied to bfncs's topic in Multi-Language Support
Seeing that Page::set isn't hookable, my best bet would be to store the "name{$language->id}" values away in a hook after Page::loaded and compare them after Pages::save.- 3 replies
-
- 2
-
-
- Multilanguage
- Module
- (and 4 more)
-
Looks like allow_url_fopen is disabled in your server's php settings, preventing file functions like copy or file_get_contents from working on URLs. If you can't change that setting, you could download the file manually into a temporary folder using curl and then assign the local path to the temp file (making sure the directory in $config->paths->tmp actually exists beforehand). A quick&dirty take: // ... $localPath = download_file($content->pt_embed_youtube, $config->paths->tmp); $content->pt_embed_youtube_pixel = $localPath; // ... function download_file($url, $tempdir) { $fp = $tempdir . basename(parse_url($url, PHP_URL_PATH)); $fh = fopen($fp, 'wb'); $curl = curl_init($url); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1); curl_setopt($curl, CURLOPT_FILE, $fh); curl_exec($curl); curl_close($curl); fclose($fh); return $fp; }
-
Sorry if this a bit of a rant. I'm in the last steps of getting our intranet "reborn" on PW, and one still unanswered question is what search backend to use. As with most intranets it holds quite a big number of office and pdf files. I've already tried a few of the major open source search engines like ElasticSearch, Solr, Sphinx and OpenSearchServer, and I found all of them lacking in two regards - for one, the documentation is all over the place so you never know if the piece you're reading even applies to the version you're using, and the implementations of their APIs are just horribly awkward and extremely picky in regards to the slightest deviations from their "standard" (which isn't really concisely documented) syntax. None of them comes with a basic permission system, which is an absolute must have. I can probably work around that with facets or the like, but still... you'd think everybody all over the world but me only indexes public webpages. Design decisions like using JSON objects that have multiple identically named properties make me doubt the sanity of those maintaining the software. Looking at all these points, I'm now also considering rolling my own on top of an InnoDB fulltext index, just re-using the text extractors I've already running in the old system (up to now only feeding the extracted plaintext into a MySQL table and doing literal searches), adding a fulltext index, setting up a lean API module for the few search variants I need and be done with it. That, of course, still leaves the topic of extracting relevant snippets open - should I write my own UDF for that, or are there (functional and maintained) third party extensions available to do just that? A question that warrants some more digging for an educated decision. I'm still a bit torn, but there's also the time factor to consider. If any of you has experiences with searches (especially with implementing visibility of search content using a group-based permission concept) and could throw in a few pointers or experiences, I'd be glad.
-
Weird/rare "SQLSTATE[HY000]: General error: 1116 Too many tables" seen
BitPoet replied to alan's topic in General Support
Huh. This looks like your MySQL server is reachable from everywhere in the internet, which is rarely a good idea. I usually bind only to localhost (if its running on the webserver itself), or add an iptables rule that only allows access from the servers I explicitely grant access to port 3306. For maintenance access, there's always the possibility to use ssh (MySQL CC has that built-in) to forward the connection to *nix systems, or VPN when using Windows hosts. One important measure to be able to pin-point problems quickly is to eliminate as much of the background noise as possible beforehand which port scans and distributed dictionary attacks tend to produce in the logs. Not letting these get to your services in the first place makes your logs much more meaningful. I don't think the database log will tell you much though, and the above lines just say that there was a connection attempt from a host that didn't resolve to a name. The most likely source of information would be the webserver log for that time, where you can see the real URL that was requested. If you can get that, also look out for POST requests in the timeframe in question. Mysterious error like yours can (I don't want to stir up panic there, but it would feel wrong not to mention it) sometimes also point to issues that arise by passing on unsanitized form values. The answer is likely much more less dangerous, but taking a look at that never hurts. Keep in mind that MySQL sits on the end of the food chain. The webserver log is your most important means to deduce what happened, then the application logs may give you further insight about how it happened. -
Weird/rare "SQLSTATE[HY000]: General error: 1116 Too many tables" seen
BitPoet replied to alan's topic in General Support
/?/ is not the url that was called, it's what the shutdown function outputs when $page isn't set. You'll have to dig into the webserver logs to see which url was called for real. -
how to styling of the output content of pagnation
BitPoet replied to adrianmak's topic in General Support
Why not write a small module that hooks before pageSave and fills a hidden summary field with the teaser generated with your function? That way, you'll also save memory by only running the function once in the backend and not with every listing of your page. -
Transfered a website to new host - DB connect error 1045
BitPoet replied to adinuno's topic in General Support
Also, make sure the host part for the user matches the web server IP (or is "%" as a wildcard). select user, host from mysql.user; lists all configured users and their allowed source hosts. If that matches, check your user's permissions. show grants for 'admin'@'192.249.113.81'; (or 'admin'@'%' if you specified a wildcard, whatever the user table showed). Check the exact database naming and, if all that fails, also reset the password. -
Have you seen this thread?
-
See the AdminCustomPages module. It helps you do exactly that.
-
using PW via command line interpreter include
BitPoet replied to bmacnaughton's topic in Getting Started
Run "php -i" from the command line. It will list all installed modules, and even more, it will give you the location of the php.ini file used by the php cli - it's not neccessarily the same as your webserver module uses. So on linux, running php -i | grep php.ini or on windows php -i | findstr php.ini will give you a line reading "Loaded Configuration File => PATH/TO/php.ini". Make sure that this file also loads all required modules. It might also be worth it to make sure that the php executable you start in fact belongs to the same installation as the library loaded into the webserver. With the popularity of bundles like [X|W|L|M]AMP I've often seen the path still pointing to outdated older php installations while the server used a far newer version. -
Pass the product to renderAddToCart: echo $modules->get("ShoppingCart")->renderAddToCart($product); The method tries to use the current page as the product page for which to render the button if none is passed as its argument, but since you're calling it from a non-product page without an sc_price field, it doesn't find anything to render.
-
For users with page-edit permissions, there's also the JSON API in ProcessPageSearch, callable like <?= $config->urls->admin ?>page/search/for?template=basic-page&include=hidden
-
This is because of assignments inside function calls, which should be avoided at all costs, e.g. in line 329 of FormHelper.module (279 is the other one): if (!empty($files = $this->files[$fieldName])) { Strict mode barks on this because assigning to passed variables like this breaks pass-by-reference. Consider these two snippets: $x = 1 byref($x); echo $x . "\n"; function byref(&$val) { $val = 3; } This correctly echoes "3". Now lets do the same and assign $x in the function call: byref($x = 1); echo $x . "\n"; function byref(&$val) { $val = 3; } Now our output is "1", which is of course not what we would have expected after assigning to a variable passed by reference - our assignment has turned the pass-by-reference into a pass-by-value. The original line should be written as: $files = $this->files[$fieldName]; if (!empty($files)) {
-
Just use $image->name as a reference. If the same image is added twice, a unique name is generated for the second one (just like when you add identically titled pages under the same parent).
-
Where to hook to override language to output for field
BitPoet replied to BitPoet's topic in Multi-Language Support
Thank you both, Soma and LostKobrakai. Textformatter and getUnformatted in combination were what I was missing! Here's the working code - short and to the point: public function formatValue(Page $page, Field $field, &$value) { if( $page->isMultiLang == 1 ) { if( $this->user->language != 'default' ) { $value = $page->getUnformatted($field->name)->getDefaultValue(); } $value = preg_replace_callback('/\{TR:([^}]+)\}/', array($this, "translate"), $value); } } -
Where to hook to override language to output for field
BitPoet replied to BitPoet's topic in Multi-Language Support
Basically, I've got migrated articles in multiple languages. The body text in the default language (German) contains translation tags in the form of "{TR:Teil}: xxx.yyy.zzz - {TR:Gehäuse}". These TR tags need to be read, the text extracted, translated and replaced (or, if accessed in the default language, just the tags stripped) so the page displays "part: xxx.yyy.zzz - housing" for English and "Teil: xxx.yyy.zzz -Gehäuse" for German. There may be a body text in English as well, so relying on the "use text from default language if empty" feature is unfortunately not sufficient. -
Where to hook to override language to output for field
BitPoet replied to BitPoet's topic in Multi-Language Support
Huh. My enthusiasm was premature. Unfortunately, this only works if I don't need to modify the text value in the default language. If I try to do the latter, I once again end up in an infinite recursion. I tried to move the default language replacement part into a textformatter, but then the value gets replaced with the original one again. I'm at a loss. -
Where to hook to override language to output for field
BitPoet replied to BitPoet's topic in Multi-Language Support
Thank you! That was exactly the place. I had to fiddle a bit because I tried at first with $page->getLanguageValue which again led to an infinite recursion, but this works: public function init() { $this->addHookBefore('FieldtypeText::formatValue', $this, 'hookBeforeFormatValue'); } public function hookBeforeFormatValue($event) { $page = $event->arguments(0); $field = $event->arguments(1); $value = $event->arguments(2); if( $field->name == 'body' && $page->isMultiLang == 1 && ! $this->user->language->isDefault() ) { $lang = $this->user->language; $this->user->language = wire('languages')->get('default'); $value = $page->get($field->name); $this->user->language = $lang; // Pattern replacement part comes here $event->return = $value; $event->replace = true; } } I've thought about making the valid field names configurable in the template definition, but that's probably a bit too steep right now. Thanks again! -
I'm a bit at a loss where to put my hooks. My requirement is to have a checkbox in the page (already there) that tells my module to pull fields (i.e. body, headline etc., but possibly others too in the near future) from the default language, no matter if there's a value set in the current language, and apply some replacement before rendering (think HannaCode-like, though with a whole dictionary database for technical terms behind it). Now, I'm unsure where to hook. I've looked through the LanguageSupport modules and tried to spy in HannaCode, but I've not been able to wrap my head around all the getLanguage..., getFormatted... and whatnot hookable methods involved. Attempts at overriding LanguageSupportFIelds::hookFieldtypeFormatValue and extending its logic somehow ended up with an endless recursion. If anybody could give me a few pointers (or method names) I should look for, this would be great.